From b834dddb11190d27874143302a7ad3ab1301b71f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 12 Jan 2026 15:50:24 +0100 Subject: [PATCH] **Refactor sRGB import option on textures** to actually handle image contents with gamma --- .../DebugMaterials/SingleColor/Decal.flax | 4 +- Content/Editor/MaterialTemplates/GUI.shader | 6 +++ Content/Editor/TexturePreviewMaterial.flax | 4 +- Content/Shaders/ColorGrading.flax | 4 +- Content/Shaders/GI/DDGI.flax | 4 +- Content/Shaders/GUI.flax | 4 +- Content/Shaders/PostProcessing.flax | 4 +- Flax.flaxproj | 2 +- .../Content/Import/TextureImportEntry.cs | 5 ++ .../Viewport/Previews/TexturePreview.cs | 30 ++++++----- Source/Engine/Core/Config/GameSettings.cpp | 11 ++++ Source/Engine/Core/Config/GraphicsSettings.h | 17 ++++-- Source/Engine/Graphics/Models/ModelData.h | 5 ++ Source/Engine/Render2D/Render2D.cpp | 54 +++++++++++++++++-- Source/Engine/Render2D/Render2D.h | 5 ++ Source/Engine/Renderer/PostProcessingPass.cpp | 15 +++++- .../Tools/ModelTool/ModelTool.Assimp.cpp | 7 +-- .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 5 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 + .../TextureTool/TextureTool.DirectXTex.cpp | 12 ++++- Source/Engine/Tools/TextureTool/TextureTool.h | 2 +- .../Tools/TextureTool/TextureTool.stb.cpp | 12 +++-- Source/Shaders/GUI.shader | 9 ++-- Source/Shaders/GUICommon.hlsl | 17 +++++- Source/Shaders/PostProcessing.shader | 10 +++- 25 files changed, 196 insertions(+), 54 deletions(-) diff --git a/Content/Editor/DebugMaterials/SingleColor/Decal.flax b/Content/Editor/DebugMaterials/SingleColor/Decal.flax index b94f22bc8..4469b7b22 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Decal.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Decal.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:588c29a4b239c32d4b125052e4054a29cf5140562e90ca6fac4d2952e03f66c7 -size 10397 +oid sha256:6b4d79e7fa619f29f28c38d56cc7513369cbcd8d71dd39cd1d8c33e5c9c97fe2 +size 10398 diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader index 22da15796..42e0179b7 100644 --- a/Content/Editor/MaterialTemplates/GUI.shader +++ b/Content/Editor/MaterialTemplates/GUI.shader @@ -38,6 +38,7 @@ struct VertexOutput #endif float4 ClipExtents : TEXCOORD3; float2 ClipOrigin : TEXCOORD4; + float2 CustomData : TEXCOORD5; // x-per-geometry type, y-features mask #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif @@ -55,6 +56,7 @@ struct PixelInput #endif float4 ClipExtents : TEXCOORD3; float2 ClipOrigin : TEXCOORD4; + float2 CustomData : TEXCOORD5; // x-per-geometry type, y-features mask #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif @@ -67,6 +69,7 @@ struct MaterialInput float3 WorldPosition; float TwoSidedSign; float2 TexCoord; + float2 CustomData; // x-per-geometry type, y-features mask #if USE_VERTEX_COLOR half4 VertexColor; #endif @@ -84,6 +87,7 @@ MaterialInput GetMaterialInput(Render2DVertex input, VertexOutput output) MaterialInput result; result.WorldPosition = output.WorldPosition; result.TexCoord = output.TexCoord; + result.CustomData = input.CustomDataAndClipOrigin.xy; #if USE_VERTEX_COLOR result.VertexColor = output.VertexColor; #endif @@ -103,6 +107,7 @@ MaterialInput GetMaterialInput(PixelInput input) MaterialInput result; result.WorldPosition = input.WorldPosition; result.TexCoord = input.TexCoord; + result.CustomData = input.CustomData; #if USE_VERTEX_COLOR result.VertexColor = input.VertexColor; #endif @@ -229,6 +234,7 @@ VertexOutput VS_GUI(Render2DVertex input) #if USE_VERTEX_COLOR output.VertexColor = input.Color; #endif + output.CustomData = input.CustomDataAndClipOrigin.xy; output.ClipOrigin = input.CustomDataAndClipOrigin.zw; output.ClipExtents = input.ClipExtents; diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index d75e19d5e..28c2d7896 100644 --- a/Content/Editor/TexturePreviewMaterial.flax +++ b/Content/Editor/TexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79de09ba0616eb6066171c2b80cdb6c4235cb52be4836d23162bb9c2585760a0 -size 11058 +oid sha256:d726a95ca50527ace0cb47eeff584ed129d69405e7068b6d1eb5a06d6fa9729a +size 11533 diff --git a/Content/Shaders/ColorGrading.flax b/Content/Shaders/ColorGrading.flax index 716e18593..f7074f20d 100644 --- a/Content/Shaders/ColorGrading.flax +++ b/Content/Shaders/ColorGrading.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bce57f0ccf6d808985f4d79cc4e15d85cae999bee598d8e93ff5b1b126bc42b8 -size 12310 +oid sha256:c0b43b79fe3ad34ebf5fa47d49e017c768b48c1bd688ba521a684bab7ef286bd +size 12311 diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 257953bf9..6d1431236 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b017cf857f443553020e4bc7c8c8c5da3a826a2514322664a023ffa6005f7a5 -size 38217 +oid sha256:c0984d39f5226f8eac725b307c88a663d54836a820ae4d4d3cffe73b8fe6d0a2 +size 38199 diff --git a/Content/Shaders/GUI.flax b/Content/Shaders/GUI.flax index d5e3f59fa..e243245be 100644 --- a/Content/Shaders/GUI.flax +++ b/Content/Shaders/GUI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fed6a05104322f61a77f6cf69b64478518b829165a2a3ab7fc151098dcd48be -size 4526 +oid sha256:6a9b9f62e4e04e9cfa15aae54a78024698780c33a81116c550a409aedfc89cae +size 4621 diff --git a/Content/Shaders/PostProcessing.flax b/Content/Shaders/PostProcessing.flax index e34c5c185..4bdb35584 100644 --- a/Content/Shaders/PostProcessing.flax +++ b/Content/Shaders/PostProcessing.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e442c2d6607e40da68e3aa9414390386d44cc7bc8c677a1f5a5e4a536857b906 -size 22688 +oid sha256:eb59d5d69de414b28dad32c673193d138709c16bd31bb8a1cf435d329a6bd1c1 +size 22997 diff --git a/Flax.flaxproj b/Flax.flaxproj index c74ec3990..aa3a8655f 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 12, "Revision": 0, - "Build": 6900 + "Build": 6901 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/Content/Import/TextureImportEntry.cs b/Source/Editor/Content/Import/TextureImportEntry.cs index e547a1b9e..a2e14aaf1 100644 --- a/Source/Editor/Content/Import/TextureImportEntry.cs +++ b/Source/Editor/Content/Import/TextureImportEntry.cs @@ -187,6 +187,11 @@ namespace FlaxEditor.Content.Import // Glossiness, metalness, ambient occlusion, displacement, height, cavity or specular _settings.Settings.Type = TextureFormatType.GrayScale; } + else if (_settings.Settings.Type == TextureFormatType.ColorRGB) + { + // Blind guess that common color texture is sRGB + _settings.Settings.sRGB = true; + } // Try to restore target asset texture import options (useful for fast reimport) Editor.TryRestoreImportOptions(ref _settings.Settings, ResultUrl); diff --git a/Source/Editor/Viewport/Previews/TexturePreview.cs b/Source/Editor/Viewport/Previews/TexturePreview.cs index 709bc953e..4c36a2725 100644 --- a/Source/Editor/Viewport/Previews/TexturePreview.cs +++ b/Source/Editor/Viewport/Previews/TexturePreview.cs @@ -410,6 +410,22 @@ namespace FlaxEditor.Viewport.Previews UpdateTextureRect(); } + /// + /// Draws the material + /// + protected void DrawMaterial(ref Rectangle rect) + { + var textureObj = _previewMaterial.GetParameterValue("Texture"); + var removeGamma = textureObj is TextureBase texture && PixelFormatExtensions.IsSRGB(texture.Format); + if (removeGamma) + Render2D.Features |= Render2D.RenderingFeatures.RemoveGamma; + + Render2D.DrawMaterial(_previewMaterial, rect); + + if (removeGamma) + Render2D.Features &= ~Render2D.RenderingFeatures.RemoveGamma; + } + private void OnMipWidgetMenuOnVisibleChanged(Control control) { if (!control.Visible) @@ -610,14 +626,9 @@ namespace FlaxEditor.Viewport.Previews /// protected override void DrawTexture(ref Rectangle rect) { - // Background Render2D.FillRectangle(rect, Color.Gray); - - // Check if has loaded asset if (_asset && _asset.IsLoaded) - { - Render2D.DrawMaterial(_previewMaterial, rect); - } + DrawMaterial(ref rect); } } @@ -665,14 +676,9 @@ namespace FlaxEditor.Viewport.Previews /// protected override void DrawTexture(ref Rectangle rect) { - // Background Render2D.FillRectangle(rect, Color.Gray); - - // Check if has loaded asset if (_asset && _asset.IsLoaded) - { - Render2D.DrawMaterial(_previewMaterial, rect); - } + DrawMaterial(ref rect); } } } diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index eb6705c9c..1f37175b4 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -50,6 +50,17 @@ void GraphicsSettings::SetUeeHDRProbes(bool value) UseHDRProbes = value; } +void GraphicsSettings::OnDeserializing(const CallbackContext& context) +{ + // [Deprecated on 9.01.2026, expires on 9.01.2028] + if (context.Modifier && context.Modifier->EngineBuild < 6901) + { + // Old projects were made in Gamma color space + GammaColorSpace = true; + MARK_CONTENT_DEPRECATED(); + } +} + IMPLEMENT_ENGINE_SETTINGS_GETTER(GraphicsSettings, Graphics); IMPLEMENT_ENGINE_SETTINGS_GETTER(NetworkSettings, Network); IMPLEMENT_ENGINE_SETTINGS_GETTER(LayersAndTagsSettings, LayersAndTags); diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index d8b35533a..80f657fec 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -127,12 +127,22 @@ public: API_FIELD(Attributes="EditorOrder(2130), Limit(256, 8192), EditorDisplay(\"Global Illumination\")") int32 GlobalSurfaceAtlasResolution = 2048; +public: + /// + /// If checked, color space workflow will use Gamma instead of Linear. Gamma color space defines colors with an applied a gamma curve (sRGB) so they are perceptually linear. + /// This makes sense when the output of the rendering represent final color values that will be presented to a non-HDR screen. + /// + API_FIELD(Attributes="EditorOrder(3000), EditorDisplay(\"Colors\")") + bool GammaColorSpace = false; + +public: /// /// The default Post Process settings. Can be overriden by PostFxVolume on a level locally, per camera or for a whole map. /// API_FIELD(Attributes="EditorOrder(10000), EditorDisplay(\"Post Process Settings\", EditorDisplayAttribute.InlineStyle)") PostProcessSettings PostProcessSettings; +public: /// /// The list of fallback fonts used for text rendering. Ignored if empty. /// @@ -144,12 +154,9 @@ private: /// Renamed UeeHDRProbes into UseHDRProbes /// [Deprecated on 12.10.2022, expires on 12.10.2024] /// - API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use UseHDRProbes instead.") bool GetUeeHDRProbes() const - { - return UseHDRProbes; - } - + API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use UseHDRProbes instead.") bool GetUeeHDRProbes() const { return UseHDRProbes; } API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use UseHDRProbes instead.") void SetUeeHDRProbes(bool value); + API_FUNCTION(Attributes="OnDeserializing", Hidden) void OnDeserializing(const CallbackContext& context); public: /// diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 26b473e09..169ff3339 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -278,6 +278,11 @@ struct FLAXENGINE_API TextureEntry /// TypeHint Type; + /// + /// Hints that texture contents are in sRGB color format. + /// + bool sRGB = false; + /// /// The texture asset identifier. /// diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 5ae034bc5..aa61603d7 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -7,6 +7,7 @@ #include "RotatedRectangle.h" #include "SpriteAtlas.h" #include "Engine/Core/Math/Matrix3x3.h" +#include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/Content.h" @@ -18,6 +19,7 @@ #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/DynamicBuffer.h" +#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" @@ -48,6 +50,9 @@ #define RENDER2D_BLUR_MAX_SAMPLES 64 +#define RENDER2D_REMOVE_GAMMA_BEGIN(t, getFormat) const bool removeGamma = IsRemoveGammaEnabled && t && PixelFormatExtensions::IsSRGB(t->getFormat()); if (removeGamma) Features |= RenderingFeatures::RemoveGamma +#define RENDER2D_REMOVE_GAMMA_END() if (removeGamma) Features &= ~RenderingFeatures::RemoveGamma + // The format for the blur effect temporary buffer #define PS_Blur_Format PixelFormat::R8G8B8A8_UNorm @@ -199,6 +204,7 @@ namespace Array Lines2; bool IsScissorsRectEmpty; bool IsScissorsRectEnabled; + bool IsRemoveGammaEnabled; // Transform // Note: we use Matrix3x3 instead of Matrix because we use only 2D transformations on CPU side @@ -683,6 +689,7 @@ void Render2D::Begin(GPUContext* context, GPUTextureView* output, GPUTextureView View = viewport; ViewProjection = viewProjection; DrawCalls.Clear(); + IsRemoveGammaEnabled = GraphicsSettings::Get()->GammaColorSpace == false; // Initialize default transform const Matrix3x3 defaultTransform = Matrix3x3::Identity; @@ -1583,6 +1590,7 @@ void Render2D::DrawRectangle(const Rectangle& rect, const Color& color1, const C void Render2D::DrawTexture(GPUTextureView* rt, const Rectangle& rect, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; + RENDER2D_REMOVE_GAMMA_BEGIN(rt, GetFormat); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillRT; @@ -1590,11 +1598,14 @@ void Render2D::DrawTexture(GPUTextureView* rt, const Rectangle& rect, const Colo drawCall.CountIB = 6; drawCall.AsRT.Ptr = rt; WriteRect(rect, color); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawTexture(GPUTexture* t, const Rectangle& rect, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall drawCall; drawCall.Type = DrawCallType::FillTexture; @@ -1603,11 +1614,14 @@ void Render2D::DrawTexture(GPUTexture* t, const Rectangle& rect, const Color& co drawCall.AsTexture.Ptr = t; DrawCalls.Add(drawCall); WriteRect(rect, color); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawTexture(TextureBase* t, const Rectangle& rect, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall drawCall; drawCall.Type = DrawCallType::FillTexture; @@ -1616,6 +1630,8 @@ void Render2D::DrawTexture(TextureBase* t, const Rectangle& rect, const Color& c drawCall.AsTexture.Ptr = t ? t->GetTexture() : nullptr; DrawCalls.Add(drawCall); WriteRect(rect, color); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color) @@ -1623,6 +1639,7 @@ void Render2D::DrawSprite(const SpriteHandle& spriteHandle, const Rectangle& rec RENDER2D_CHECK_RENDERING_STATE; if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) return; + RENDER2D_REMOVE_GAMMA_BEGIN(spriteHandle.Atlas->GetTexture(), Format); Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index); Render2DDrawCall& drawCall = DrawCalls.AddOne(); @@ -1631,11 +1648,14 @@ void Render2D::DrawSprite(const SpriteHandle& spriteHandle, const Rectangle& rec drawCall.CountIB = 6; drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture(); WriteRect(rect, color, sprite->Area.GetUpperLeft(), sprite->Area.GetBottomRight()); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawTexturePoint(GPUTexture* t, const Rectangle& rect, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexturePoint; @@ -1643,6 +1663,8 @@ void Render2D::DrawTexturePoint(GPUTexture* t, const Rectangle& rect, const Colo drawCall.CountIB = 6; drawCall.AsTexture.Ptr = t; WriteRect(rect, color); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color) @@ -1650,6 +1672,7 @@ void Render2D::DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle RENDER2D_CHECK_RENDERING_STATE; if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) return; + RENDER2D_REMOVE_GAMMA_BEGIN(spriteHandle.Atlas->GetTexture(), Format); Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index); Render2DDrawCall& drawCall = DrawCalls.AddOne(); @@ -1658,11 +1681,14 @@ void Render2D::DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle drawCall.CountIB = 6; drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture(); WriteRect(rect, color, sprite->Area.GetUpperLeft(), sprite->Area.GetBottomRight()); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::Draw9SlicingTexture(TextureBase* t, const Rectangle& rect, const Float4& border, const Float4& borderUVs, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall drawCall; drawCall.Type = DrawCallType::FillTexture; @@ -1676,6 +1702,7 @@ void Render2D::Draw9SlicingTexture(TextureBase* t, const Rectangle& rect, const void Render2D::Draw9SlicingTexturePoint(TextureBase* t, const Rectangle& rect, const Float4& border, const Float4& borderUVs, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall drawCall; drawCall.Type = DrawCallType::FillTexturePoint; @@ -1691,6 +1718,7 @@ void Render2D::Draw9SlicingSprite(const SpriteHandle& spriteHandle, const Rectan RENDER2D_CHECK_RENDERING_STATE; if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) return; + RENDER2D_REMOVE_GAMMA_BEGIN(spriteHandle.Atlas->GetTexture(), Format); Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index); Render2DDrawCall& drawCall = DrawCalls.AddOne(); @@ -1699,6 +1727,8 @@ void Render2D::Draw9SlicingSprite(const SpriteHandle& spriteHandle, const Rectan drawCall.CountIB = 6 * 9; drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture(); Write9SlicingRect(rect, color, border, borderUVs, sprite->Area.Location, sprite->Area.Size); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::Draw9SlicingSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Float4& border, const Float4& borderUVs, const Color& color) @@ -1706,6 +1736,7 @@ void Render2D::Draw9SlicingSpritePoint(const SpriteHandle& spriteHandle, const R RENDER2D_CHECK_RENDERING_STATE; if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) return; + RENDER2D_REMOVE_GAMMA_BEGIN(spriteHandle.Atlas->GetTexture(), Format); Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index); Render2DDrawCall& drawCall = DrawCalls.AddOne(); @@ -1714,6 +1745,8 @@ void Render2D::Draw9SlicingSpritePoint(const SpriteHandle& spriteHandle, const R drawCall.CountIB = 6 * 9; drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture(); Write9SlicingRect(rect, color, border, borderUVs, sprite->Area.Location, sprite->Area.Size); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawCustom(GPUTexture* t, const Rectangle& rect, GPUPipelineState* ps, const Color& color) @@ -1721,6 +1754,7 @@ void Render2D::DrawCustom(GPUTexture* t, const Rectangle& rect, GPUPipelineState RENDER2D_CHECK_RENDERING_STATE; if (ps == nullptr || !ps->IsValid()) return; + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::Custom; @@ -1729,6 +1763,8 @@ void Render2D::DrawCustom(GPUTexture* t, const Rectangle& rect, GPUPipelineState drawCall.AsCustom.Tex = t; drawCall.AsCustom.Pso = ps; WriteRect(rect, color); + + RENDER2D_REMOVE_GAMMA_END(); } #if RENDER2D_USE_LINE_AA @@ -1949,6 +1985,9 @@ void Render2D::DrawMaterial(MaterialBase* material, const Rectangle& rect, const if (material == nullptr || !material->IsReady() || !material->IsGUI()) return; + // Auto-remove gamma flag if it's disabled + Features &= IsRemoveGammaEnabled ? ~RenderingFeatures::None : ~RenderingFeatures::RemoveGamma; + Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::Material; drawCall.StartIB = IBIndex; @@ -2031,15 +2070,17 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices RENDER2D_CHECK_RENDERING_STATE; CHECK(vertices.Length() % 3 == 0); CHECK(vertices.Length() == uvs.Length()); + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexture; drawCall.StartIB = IBIndex; drawCall.CountIB = vertices.Length(); drawCall.AsTexture.Ptr = t; - for (int32 i = 0; i < vertices.Length(); i += 3) WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], uvs[i], uvs[i + 1], uvs[i + 2]); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs, const Color& color) @@ -2047,15 +2088,17 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices RENDER2D_CHECK_RENDERING_STATE; CHECK(vertices.Length() % 3 == 0); CHECK(vertices.Length() == uvs.Length()); + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexture; drawCall.StartIB = IBIndex; drawCall.CountIB = vertices.Length(); drawCall.AsTexture.Ptr = t; - for (int32 i = 0; i < vertices.Length(); i += 3) WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], uvs[i], uvs[i + 1], uvs[i + 2], color, color, color); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs, const Span& colors) @@ -2064,15 +2107,17 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices CHECK(vertices.Length() % 3 == 0); CHECK(vertices.Length() == uvs.Length()); CHECK(vertices.Length() == colors.Length()); + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexture; drawCall.StartIB = IBIndex; drawCall.CountIB = vertices.Length(); drawCall.AsTexture.Ptr = t; - for (int32 i = 0; i < vertices.Length(); i += 3) WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], uvs[i], uvs[i + 1], uvs[i + 2], colors[i], colors[i + 1], colors[i + 2]); + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& indices, const Span& vertices, const Span& uvs, const Span& colors) @@ -2080,6 +2125,7 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& indices, RENDER2D_CHECK_RENDERING_STATE; CHECK(vertices.Length() == uvs.Length()); CHECK(vertices.Length() == colors.Length()); + RENDER2D_REMOVE_GAMMA_BEGIN(t, Format); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexture; @@ -2094,6 +2140,8 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& indices, const uint16 i2 = indices.Get()[i++]; WriteTri(vertices[i0], vertices[i1], vertices[i2], uvs[i0], uvs[i1], uvs[i2], colors[i0], colors[i1], colors[i2]); } + + RENDER2D_REMOVE_GAMMA_END(); } void Render2D::FillTriangles(const Span& vertices, const Color& color) diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 69b6aea1c..7002d391e 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -49,6 +49,11 @@ API_CLASS(Static) class FLAXENGINE_API Render2D /// Enables automatic characters usage from fallback fonts. /// FallbackFonts = 2, + + /// + /// Enables additional sRGB to linear color space conversion when drawing sRGB textures. This ensures that images imported with sRGB enabled in Linear workflow will be properly rendered into screen. + /// + RemoveGamma = 4, }; struct CustomData diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 5ac204523..2bac6bf76 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -2,6 +2,7 @@ #include "PostProcessingPass.h" #include "RenderList.h" +#include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUContext.h" @@ -11,6 +12,8 @@ #define GB_RADIUS 6 #define GB_KERNEL_SIZE (GB_RADIUS * 2 + 1) +#define OUTPUT_LINEAR 0 // Copies scene color directly to the output +#define OUTPUT_SRGB 1 // Converts scene color from linear to sRGB GPU_CB_STRUCT(Data{ float BloomIntensity; // Overall bloom strength multiplier @@ -48,7 +51,7 @@ GPU_CB_STRUCT(Data{ float ChromaticDistortion; float Time; - float Dummy1; + uint32 OutputColorSpace; float PostExposure; float VignetteIntensity; float LensDirtIntensity; @@ -360,6 +363,16 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, data.InputSize = Float2(static_cast(w1), static_cast(h1)); data.InvInputSize = Float2(1.0f / static_cast(w1), 1.0f / static_cast(h1)); data.InputAspect = static_cast(w1) / h1; + if (GraphicsSettings::Get()->GammaColorSpace) + { + // Gamma-space colors always present image 'as-is' + data.OutputColorSpace = OUTPUT_LINEAR; + } + else + { + // Convert linear scene color into the display's sRGB curve + data.OutputColorSpace = OUTPUT_SRGB; + } context->UpdateCB(cb0, &data); context->BindCB(0, cb0); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index b1340bfa7..7ede58373 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -463,7 +463,7 @@ bool ImportTexture(ModelData& result, AssimpImporterData& data, aiString& aFilen return true; } -bool ImportMaterialTexture(ModelData& result, AssimpImporterData& data, const aiMaterial* aMaterial, aiTextureType aTextureType, int32& textureIndex, TextureEntry::TypeHint type) +bool ImportMaterialTexture(ModelData& result, AssimpImporterData& data, const aiMaterial* aMaterial, aiTextureType aTextureType, int32& textureIndex, TextureEntry::TypeHint type, bool sRGB = false) { aiString aFilename; if (aMaterial->GetTexture(aTextureType, 0, &aFilename, nullptr, nullptr, nullptr, nullptr) == AI_SUCCESS) @@ -503,6 +503,7 @@ bool ImportMaterialTexture(ModelData& result, AssimpImporterData& data, const ai auto& texture = result.Textures.AddOne(); texture.FilePath = path; texture.Type = type; + texture.sRGB = sRGB; texture.AssetID = Guid::Empty; return true; } @@ -549,12 +550,12 @@ bool ImportMaterials(ModelData& result, AssimpImporterData& data, String& errorM if (EnumHasAnyFlags(data.Options.ImportTypes, ImportDataTypes::Textures)) { - ImportMaterialTexture(result, data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB); + ImportMaterialTexture(result, data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB, true); ImportMaterialTexture(result, data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB); ImportMaterialTexture(result, data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals); ImportMaterialTexture(result, data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA); ImportMaterialTexture(result, data, aMaterial, aiTextureType_METALNESS, materialSlot.Metalness.TextureIndex, TextureEntry::TypeHint::ColorRGB); - ImportMaterialTexture(result, data, aMaterial, aiTextureType_DIFFUSE_ROUGHNESS, materialSlot.Roughness.TextureIndex, TextureEntry::TypeHint::ColorRGB); + ImportMaterialTexture(result, data, aMaterial, aiTextureType_DIFFUSE_ROUGHNESS, materialSlot.Roughness.TextureIndex, TextureEntry::TypeHint::ColorRGB, true); if (materialSlot.Roughness.TextureIndex != -1 && (data.Path.EndsWith(TEXT(".gltf")) || data.Path.EndsWith(TEXT(".glb")))) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 81ce46d6c..acf18cb18 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -196,7 +196,7 @@ struct OpenFbxImporterData #endif } - bool ImportMaterialTexture(ModelData& result, const ofbx::Material* mat, ofbx::Texture::TextureType textureType, int32& textureIndex, TextureEntry::TypeHint type) const + bool ImportMaterialTexture(ModelData& result, const ofbx::Material* mat, ofbx::Texture::TextureType textureType, int32& textureIndex, TextureEntry::TypeHint type, bool sRGB = false) const { const ofbx::Texture* tex = mat->getTexture(textureType); if (tex) @@ -225,6 +225,7 @@ struct OpenFbxImporterData auto& texture = result.Textures.AddOne(); texture.FilePath = path; texture.Type = type; + texture.sRGB = sRGB; texture.AssetID = Guid::Empty; return true; } @@ -251,7 +252,7 @@ struct OpenFbxImporterData if (EnumHasAnyFlags(Options.ImportTypes, ImportDataTypes::Textures)) { - ImportMaterialTexture(result, mat, ofbx::Texture::DIFFUSE, material.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB); + ImportMaterialTexture(result, mat, ofbx::Texture::DIFFUSE, material.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB, true); ImportMaterialTexture(result, mat, ofbx::Texture::EMISSIVE, material.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB); ImportMaterialTexture(result, mat, ofbx::Texture::NORMAL, material.Normals.TextureIndex, TextureEntry::TypeHint::Normals); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 843822b98..aedaf12a4 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1334,6 +1334,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option String assetPath = GetAdditionalImportPath(autoImportOutput, importedFileNames, StringUtils::GetFileNameWithoutExtension(texture.FilePath)); #if COMPILE_WITH_ASSETS_IMPORTER TextureTool::Options textureOptions; + textureOptions.sRGB = texture.sRGB; switch (texture.Type) { case TextureEntry::TypeHint::ColorRGB: @@ -1344,6 +1345,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option break; case TextureEntry::TypeHint::Normals: textureOptions.Type = TextureFormatType::NormalMap; + textureOptions.sRGB = false; break; } AssetsImportingManager::ImportIfEdited(texture.FilePath, assetPath, texture.AssetID, &textureOptions); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index d7199a470..cb758babd 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -5,6 +5,7 @@ #include "TextureTool.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Platform/File.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Platform/ConditionVariable.h" @@ -665,8 +666,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path if (sourceWidth != width || sourceHeight != height) { // During resizing we need to keep texture aspect ratio - const bool keepAspectRatio = options.KeepAspectRatio; - if (keepAspectRatio) + if (options.KeepAspectRatio) { const float aspectRatio = static_cast(sourceWidth) / sourceHeight; if (width >= height) @@ -776,6 +776,14 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path for (size_t i = 0; i < currentImage->GetImageCount(); i++) ((DirectX::Image*)currentImage->GetImages())[i].format = sourceDxgiFormat; } + // Import as sRGB data for Linear color space + else if (options.sRGB && !GraphicsSettings::Get()->GammaColorSpace) + { + sourceDxgiFormat = ToDxgiFormat(PixelFormatExtensions::TosRGB(::ToPixelFormat(sourceDxgiFormat))); + ((DirectX::TexMetadata&)currentImage->GetMetadata()).format = sourceDxgiFormat; + for (size_t i = 0; i < currentImage->GetImageCount(); i++) + ((DirectX::Image*)currentImage->GetImages())[i].format = sourceDxgiFormat; + } // Remove alpha if source texture has it but output should not, valid for compressed output only (DirectX seams to use alpha to pre-multiply colors because BC1 format has no place for alpha) if (!keepAsIs && DirectX::HasAlpha(sourceDxgiFormat) && options.Type == TextureFormatType::ColorRGB && options.Compress) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index 97199680c..53f6c13a0 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -45,7 +45,7 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool API_FIELD(Attributes="EditorOrder(40)") bool IndependentChannels = false; - // True if use sRGB format for texture data. Recommended for color maps and diffuse color textures. + // If checked, indicates that input file should be loaded as an sRGB image. Common for color maps and diffuse/albedo textures. API_FIELD(Attributes="EditorOrder(50), EditorDisplay(null, \"sRGB\")") bool sRGB = false; diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index 7c74ef3f2..99ee78780 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureData.h" @@ -107,7 +108,6 @@ static TextureData const* stbDecompress(const TextureData& textureData, TextureD decompressedData->Data.Allocate(decompressedData->DepthPitch); byte* decompressedBytes = decompressedData->Data.Get(); - Color32 colors[16]; int32 blocksWidth = textureData.Width / 4; int32 blocksHeight = textureData.Height / 4; const TextureMipData* blocksData = textureData.GetData(0, 0); @@ -503,8 +503,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu if (sourceWidth != width || sourceHeight != height) { // During resizing we need to keep texture aspect ratio - const bool keepAspectRatio = options.KeepAspectRatio; // TODO: expose as import option - if (keepAspectRatio) + if (options.KeepAspectRatio) { const float aspectRatio = static_cast(sourceWidth) / sourceHeight; if (width >= height) @@ -524,7 +523,6 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu } // Cache data - float alphaThreshold = 0.3f; bool isPowerOfTwo = Math::IsPowerOfTwo(width) && Math::IsPowerOfTwo(height); PixelFormat targetFormat = ToPixelFormat(options.Type, width, height, options.Compress); if (options.sRGB) @@ -552,6 +550,12 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu return true; } + // Import as sRGB data for Linear color space + if (options.sRGB && !GraphicsSettings::Get()->GammaColorSpace) + { + textureData.Format = PixelFormatExtensions::TosRGB(textureData.Format); + } + if (options.FlipX) { // TODO: impl this diff --git a/Source/Shaders/GUI.shader b/Source/Shaders/GUI.shader index e9eedd4a6..5d02cd60e 100644 --- a/Source/Shaders/GUI.shader +++ b/Source/Shaders/GUI.shader @@ -28,7 +28,7 @@ VS2PS VS(Render2DVertex input) VS2PS output; // Render2D::RenderingFeatures::VertexSnapping - if ((int)input.CustomDataAndClipOrigin.y & 1) + if ((int)input.CustomDataAndClipOrigin.y & RENDER2D_FEATURE_VERTEX_SNAPPING) input.Position = (float2)(int2)input.Position; output.Position = mul(float4(input.Position, 0, 1), ViewProjection); @@ -45,23 +45,20 @@ META_PS(true, FEATURE_LEVEL_ES2) float4 PS_Image(VS2PS input) : SV_Target0 { PerformClipping(input); - - return Image.Sample(SamplerLinearClamp, input.TexCoord) * input.Color; + return GetImageColor(Image.Sample(SamplerLinearClamp, input.TexCoord), input.CustomData) * input.Color; } META_PS(true, FEATURE_LEVEL_ES2) float4 PS_ImagePoint(VS2PS input) : SV_Target0 { PerformClipping(input); - - return Image.Sample(SamplerPointClamp, input.TexCoord) * input.Color; + return GetImageColor(Image.Sample(SamplerPointClamp, input.TexCoord), input.CustomData) * input.Color; } META_PS(true, FEATURE_LEVEL_ES2) float4 PS_Color(VS2PS input) : SV_Target0 { PerformClipping(input); - return input.Color; } diff --git a/Source/Shaders/GUICommon.hlsl b/Source/Shaders/GUICommon.hlsl index 14c8b1327..1aca05862 100644 --- a/Source/Shaders/GUICommon.hlsl +++ b/Source/Shaders/GUICommon.hlsl @@ -4,9 +4,15 @@ #define __GUI_COMMON__ #include "./Flax/Common.hlsl" +#include "./Flax/GammaCorrectionCommon.hlsl" #define CLIPPING_ENABLE 1 +// Render2D::RenderingFeatures +#define RENDER2D_FEATURE_VERTEX_SNAPPING 1 +#define RENDER2D_FEATURE_FALLBACK_FONTS 2 +#define RENDER2D_FEATURE_REMOVE_GAMMA 4 + struct Render2DVertex { float2 Position : POSITION0; @@ -21,7 +27,7 @@ struct VS2PS float4 Position : SV_Position; float4 Color : COLOR0; float2 TexCoord : TEXCOORD0; - float2 CustomData : TEXCOORD1; + float2 CustomData : TEXCOORD1; // x-per-geometry type, y-features mask float4 ClipExtents : TEXCOORD2; float4 ClipOriginAndPos : TEXCOORD3; }; @@ -54,4 +60,13 @@ void PerformClipping(VS2PS input) PerformClipping(input.ClipOriginAndPos.xy, input.ClipOriginAndPos.zw, input.ClipExtents); } +float4 GetImageColor(float4 color, float2 customData) +{ + if ((int)customData.y & RENDER2D_FEATURE_REMOVE_GAMMA) + { + color.rgb = LinearToSrgb(color.rgb); + } + return color; +} + #endif diff --git a/Source/Shaders/PostProcessing.shader b/Source/Shaders/PostProcessing.shader index aa70833d2..e79e0138f 100644 --- a/Source/Shaders/PostProcessing.shader +++ b/Source/Shaders/PostProcessing.shader @@ -26,6 +26,8 @@ #define GB_RADIUS 6 #define GB_KERNEL_SIZE (GB_RADIUS * 2 + 1) +#define OUTPUT_LINEAR 0 // Copies scene color directly to the output +#define OUTPUT_SRGB 1 // Converts scene color from linear to sRGB #ifndef NO_GRADING_LUT #define NO_GRADING_LUT 0 @@ -71,7 +73,7 @@ float2 InvInputSize; float ChromaticDistortion; float Time; -float Dummy1; +uint OutputColorSpace; float PostExposure; float VignetteIntensity; float LensDirtIntensity; @@ -695,6 +697,12 @@ float4 PS_Composite(Quad_VS2PS input) : SV_Target color.rgb = ColorLookupTable(color.rgb); #endif + if (OutputColorSpace == OUTPUT_SRGB) + { + // Convert into output display color space (sRGB) + color.rgb = LinearToSrgb(color.rgb); + } + // Film Grain BRANCH if (GrainAmount > 0)