From 4f08db0b236d3f01d7c8ead0a53f5ec22cc5e416 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:30:27 +0200 Subject: [PATCH] implement sRGB output gamma, and user settings --- src/Cafe/HW/Latte/Core/Latte.h | 2 + .../HW/Latte/Renderer/RendererOuputShader.cpp | 27 +++++++----- .../HW/Latte/Renderer/RendererOuputShader.h | 3 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 6 ++- src/Cafe/OS/libs/gx2/GX2_Misc.cpp | 9 +++- src/config/ActiveSettings.cpp | 13 ++++++ src/config/ActiveSettings.h | 5 +++ src/config/CemuConfig.cpp | 10 +++++ src/config/CemuConfig.h | 5 +++ src/gui/wxgui/GeneralSettings2.cpp | 43 +++++++++++++++++++ src/gui/wxgui/GeneralSettings2.h | 7 +++ 11 files changed, 114 insertions(+), 16 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index 2636467b..3495d1b9 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -54,6 +54,8 @@ struct LatteGPUState_t // temporary (replace with proper solution later) bool tvBufferUsesSRGB; bool drcBufferUsesSRGB; + float tvGamma = 0.0f; + float drcGamma = 0.0f; // draw state bool activeShaderHasError; // if try, at least one currently bound shader stage has an error and cannot be used for drawing bool repeatTextureInitialization; // if set during rendertarget or texture initialization, repeat the process (textures likely have been invalidated) diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp index 84e5d9a7..c4274164 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp @@ -1,5 +1,6 @@ #include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "config/ActiveSettings.h" const std::string RendererOutputShader::s_copy_shader_source = R"( @@ -137,12 +138,14 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con m_uniformLocations[0].m_loc_outputResolution = m_vertex_shader->GetUniformLocation("outputResolution"); m_uniformLocations[0].m_loc_applySRGBEncoding = m_vertex_shader->GetUniformLocation("applySRGBEncoding"); m_uniformLocations[0].m_loc_targetGamma = m_fragment_shader->GetUniformLocation("targetGamma"); + m_uniformLocations[0].m_loc_displayGamma = m_fragment_shader->GetUniformLocation("displayGamma"); m_uniformLocations[1].m_loc_textureSrcResolution = m_fragment_shader->GetUniformLocation("textureSrcResolution"); m_uniformLocations[1].m_loc_nativeResolution = m_fragment_shader->GetUniformLocation("nativeResolution"); m_uniformLocations[1].m_loc_outputResolution = m_fragment_shader->GetUniformLocation("outputResolution"); m_uniformLocations[1].m_loc_applySRGBEncoding = m_fragment_shader->GetUniformLocation("applySRGBEncoding"); m_uniformLocations[1].m_loc_targetGamma = m_fragment_shader->GetUniformLocation("targetGamma"); + m_uniformLocations[1].m_loc_displayGamma = m_fragment_shader->GetUniformLocation("displayGamma"); } } @@ -180,7 +183,12 @@ void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_ if (locations.m_loc_targetGamma != -1) { - shader->SetUniform1f(locations.m_loc_targetGamma, GetTargetGamma(padView)); // TODO: hook up to user-pref override or GX calls + shader->SetUniform1f(locations.m_loc_targetGamma, padView ? ActiveSettings::GetDRCGamma() : ActiveSettings::GetTVGamma()); + } + + if (locations.m_loc_displayGamma != -1) + { + shader->SetUniform1f(locations.m_loc_displayGamma, GetConfig().userDisplayGamma); } }; @@ -307,6 +315,7 @@ layout(push_constant) uniform pc { vec2 outputResolution; bool applySRGBEncoding; // true = app requested sRGB encoding float targetGamma; + float displayGamma; }; #else uniform vec2 textureSrcResolution; @@ -314,6 +323,7 @@ uniform vec2 nativeResolution; uniform vec2 outputResolution; uniform bool applySRGBEncoding; uniform float targetGamma; +uniform float displayGamma; #endif layout(location = 0) smooth in vec2 passUV; @@ -340,11 +350,13 @@ void main() { outputShader(); // sets colorOut0 if(applySRGBEncoding) - { - colorOut0 = vec4(sRGBEncode(colorOut0.xyz), 1.0f); - } + colorOut0 = vec4(sRGBEncode(colorOut0.rgb), 1.0f); + + if (displayGamma > 0.0f) + colorOut0 = pow(colorOut0, vec4(targetGamma / displayGamma) ); + else + colorOut0 = vec4( sRGBEncode( pow(colorOut0.rgb, vec3(targetGamma)) ), 1.0f); - colorOut0 = pow(colorOut0, vec4(targetGamma / 2.2f) ); } )" + shaderSrc; @@ -384,8 +396,3 @@ void RendererOutputShader::ShutdownStatic() delete s_hermit_shader; delete s_hermit_shader_ud; } - -float RendererOutputShader::GetTargetGamma(const bool padView) -{ - return 2.4f; -} diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h index b6ad3e28..44373791 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h @@ -46,8 +46,6 @@ public: static std::string PrependFragmentPreamble(const std::string& shaderSrc); - static float GetTargetGamma(const bool padView); - protected: std::unique_ptr m_vertex_shader; std::unique_ptr m_fragment_shader; @@ -59,6 +57,7 @@ protected: sint32 m_loc_outputResolution = -1; sint32 m_loc_applySRGBEncoding = -1; sint32 m_loc_targetGamma = -1; + sint32 m_loc_displayGamma = -1; } m_uniformLocations[2]{}; private: diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index c5c43e75..d78b465d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -2661,7 +2661,7 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet .offset = 0, .size = 3 * sizeof(float) * 2 // 3 vec2's + 4 // + 1 VkBool32 - + 4 // + 1 float + + 4 * 2 // + 2 float }; VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; @@ -3055,6 +3055,7 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu Vector2f vecs[3]; VkBool32 applySRGBEncoding; float targetGamma; + float displayGamma; } pushData; // textureSrcResolution @@ -3072,7 +3073,8 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu pushData.vecs[2] = {(float)imageWidth,(float)imageHeight}; pushData.applySRGBEncoding = padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB; - pushData.targetGamma = RendererOutputShader::GetTargetGamma(padView); + pushData.targetGamma = padView ? ActiveSettings::GetDRCGamma() : ActiveSettings::GetTVGamma(); + pushData.displayGamma = GetConfig().userDisplayGamma; vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pushData), &pushData); diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp index e7830cd8..564b787e 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp @@ -206,8 +206,12 @@ namespace GX2 void GX2SetTVGamma(float gamma) { - if (abs(gamma - 1.0f) > 0.01f) - cemuLog_logDebug(LogType::Force, "TV gamma set to {} which is not supported", gamma); + LatteGPUState.tvGamma = (1.0f - gamma); + } + + void GX2SetDRCGamma(float gamma) + { + LatteGPUState.drcGamma = (1.0f - gamma); } bool GX2GetLastFrame(uint32 deviceId, GX2Texture* textureOut) @@ -307,6 +311,7 @@ namespace GX2 cafeExportRegister("gx2", GX2SetTVBuffer, LogType::GX2); cafeExportRegister("gx2", GX2SetTVGamma, LogType::GX2); + cafeExportRegister("gx2", GX2SetDRCGamma, LogType::GX2); cafeExportRegister("gx2", GX2GetLastFrame, LogType::GX2); cafeExportRegister("gx2", GX2GetLastFrameGammaA, LogType::GX2); diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 58a83a15..3ecd8855 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -6,6 +6,7 @@ #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include "util/helpers/helpers.h" +#include "Cafe/HW/Latte/Core/Latte.h" void ActiveSettings::SetPaths(bool isPortableMode, const fs::path& executablePath, @@ -112,6 +113,18 @@ GraphicAPI ActiveSettings::GetGraphicsAPI() return api; } +float ActiveSettings::GetTVGamma() +{ + const auto& config = GetConfig(); + return config.overrideGammaValue.GetValue() + LatteGPUState.tvGamma * !config.overrideAppGammaPreference.GetValue(); +} + +float ActiveSettings::GetDRCGamma() +{ + const auto& config = GetConfig(); + return config.overrideGammaValue.GetValue() + LatteGPUState.drcGamma * !config.overrideAppGammaPreference.GetValue(); +} + bool ActiveSettings::AudioOutputOnlyAux() { return s_audio_aux_only; diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h index c01170e2..20838f4a 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -96,6 +96,11 @@ public: [[nodiscard]] static bool WaitForGX2DrawDoneEnabled(); [[nodiscard]] static GraphicAPI GetGraphicsAPI(); + // gamma + [[nodiscard]] static float GetTVGamma(); + [[nodiscard]] static float GetDRCGamma(); + [[nodiscard]] static float GetPCDisplayGamma(); + // audio [[nodiscard]] static bool AudioOutputOnlyAux(); static void EnableAudioOnlyAux(bool state); diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 620f005e..d01f554d 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -129,6 +129,13 @@ XMLConfigParser CemuConfig::Load(XMLConfigParser& parser) graphic_api = graphic.get("api", kOpenGL); graphic.get("device", graphic_device_uuid); vsync = graphic.get("VSync", 0); + overrideAppGammaPreference = graphic.get("OverrideAppGammaPreference", false); + overrideGammaValue = graphic.get("OverrideGammaValue", 2.2f); + if(overrideGammaValue < 0) + overrideGammaValue = 2.2f; + userDisplayGamma = graphic.get("UserDisplayGamma", 2.2f); + if(userDisplayGamma < 0) + userDisplayGamma = 2.2f; gx2drawdone_sync = graphic.get("GX2DrawdoneSync", true); upscale_filter = graphic.get("UpscaleFilter", kBicubicHermiteFilter); downscale_filter = graphic.get("DownscaleFilter", kLinearFilter); @@ -346,6 +353,9 @@ XMLConfigParser CemuConfig::Save(XMLConfigParser& parser) graphic.set("api", graphic_api); graphic.set("device", graphic_device_uuid); graphic.set("VSync", vsync); + graphic.set("OverrideAppGammaPreference", overrideAppGammaPreference); + graphic.set("OverrideGammaValue", overrideGammaValue); + graphic.set("UserDisplayGamma", userDisplayGamma); graphic.set("GX2DrawdoneSync", gx2drawdone_sync); //graphic.set("PrecompiledShaders", precompiled_shaders.GetValue()); graphic.set("UpscaleFilter", upscale_filter); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index ff892fb8..904b81cb 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -389,6 +389,11 @@ struct CemuConfig ConfigValue render_upside_down{ false }; ConfigValue async_compile{ true }; + // Gamma + ConfigValue overrideAppGammaPreference{ false }; + ConfigValue overrideGammaValue{ 2.2f }; + ConfigValue userDisplayGamma { 2.2f }; // 0 = sRGB, >0 gamma + ConfigValue vk_accurate_barriers{ true }; struct diff --git a/src/gui/wxgui/GeneralSettings2.cpp b/src/gui/wxgui/GeneralSettings2.cpp index f937549d..ec7ab0a2 100644 --- a/src/gui/wxgui/GeneralSettings2.cpp +++ b/src/gui/wxgui/GeneralSettings2.cpp @@ -361,6 +361,28 @@ wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook) m_vsync->SetToolTip(_("Controls the vsync state")); row->Add(m_vsync, 0, wxALL, 5); + row->Add(new wxStaticText(box, wxID_ANY, _("Override gamma")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_overrideGamma = new wxCheckBox(box, wxID_ANY, "", wxDefaultPosition, {230, -1}); + m_overrideGamma->SetToolTip(_("Ignore app gamma preference")); + row->Add(m_overrideGamma, 0, wxALL, 5); + + row->Add(new wxStaticText(box, wxID_ANY, _("Gamma")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_overrideGammaValue = new wxSpinCtrlDouble(box, wxID_ANY, "2.2f", wxDefaultPosition, {230, -1}, wxSP_ARROW_KEYS, 0.1f, 4.0f, 2.2f, 0.1f); + row->Add(m_overrideGammaValue, 0, wxALL, 5); + + + row->Add(new wxStaticText(box, wxID_ANY, _("Display Gamma")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + wxBoxSizer* test = new wxBoxSizer(wxHORIZONTAL); + row->Add(test); + m_userDisplayGamma = new wxSpinCtrlDouble(box, wxID_ANY, "2.2f", wxDefaultPosition, {230, -1}, wxSP_ARROW_KEYS, 0.1f, 4.0f, 2.2f, 0.1f); + m_userDisplayisSRGB = new wxCheckBox(box, wxID_ANY, "sRGB", wxDefaultPosition, wxDefaultSize); + m_userDisplayisSRGB->SetToolTip(_("Select this if your screen is standard compliant sRGB.\nMore accurate but may result in banding and/or crushed shadows.")); + m_userDisplayisSRGB->Bind(wxEVT_CHECKBOX, &GeneralSettings2::OnUserDisplaySRGBSelected, this); + + test->Add(m_userDisplayGamma, 0, wxALL, 5); + test->Add(m_userDisplayisSRGB, 0, wxALL, 5); + box_sizer->Add(row, 0, wxEXPAND, 5); auto* graphic_misc_row = new wxFlexGridSizer(0, 2, 0, 0); @@ -1104,6 +1126,9 @@ void GeneralSettings2::StoreConfig() config.vsync = m_vsync->GetSelection(); + config.overrideAppGammaPreference = m_overrideGamma->IsChecked(); + config.overrideGammaValue = m_overrideGammaValue->GetValue(); + config.userDisplayGamma = m_userDisplayGamma->GetValue() * !m_userDisplayisSRGB->GetValue(); config.gx2drawdone_sync = m_gx2drawdone_sync->IsChecked(); config.async_compile = m_async_compile->IsChecked(); @@ -1685,6 +1710,15 @@ void GeneralSettings2::ApplyConfig() // graphics m_graphic_api->SetSelection(config.graphic_api); m_vsync->SetSelection(config.vsync); + m_overrideGamma->SetValue(config.overrideAppGammaPreference); + m_overrideGammaValue->SetValue(config.overrideGammaValue); + m_userDisplayisSRGB->SetValue(config.userDisplayGamma == 0.0f); + m_userDisplayGamma->SetValue(config.userDisplayGamma); + if(m_userDisplayisSRGB->GetValue()) + { + m_userDisplayGamma->Disable(); + m_userDisplayGamma->SetValue(2.2f); + } m_async_compile->SetValue(config.async_compile); m_gx2drawdone_sync->SetValue(config.gx2drawdone_sync); m_upscale_filter->SetSelection(config.upscale_filter); @@ -2040,6 +2074,15 @@ void GeneralSettings2::OnGraphicAPISelected(wxCommandEvent& event) HandleGraphicsApiSelection(); } +void GeneralSettings2::OnUserDisplaySRGBSelected(wxCommandEvent& event) +{ + m_userDisplayGamma->SetValue(2.2f); + if(event.GetInt()) + m_userDisplayGamma->Disable(); + else + m_userDisplayGamma->Enable(); +} + void GeneralSettings2::OnAddPathClicked(wxCommandEvent& event) { wxDirDialog path_dialog(this, _("Select a directory containing games."), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); diff --git a/src/gui/wxgui/GeneralSettings2.h b/src/gui/wxgui/GeneralSettings2.h index fb0cfe87..fd0a0dc5 100644 --- a/src/gui/wxgui/GeneralSettings2.h +++ b/src/gui/wxgui/GeneralSettings2.h @@ -56,6 +56,11 @@ private: // Graphics wxChoice* m_graphic_api, * m_graphic_device; wxChoice* m_vsync; + wxCheckBox* m_overrideGamma; + wxSpinCtrlDouble* m_overrideGammaValue; + wxSpinCtrlDouble* m_userDisplayGamma; + wxCheckBox* m_userDisplayisSRGB; + wxCheckBox *m_async_compile, *m_gx2drawdone_sync; wxRadioBox* m_upscale_filter, *m_downscale_filter, *m_fullscreen_scaling; wxChoice* m_overlay_position, *m_notification_position, *m_overlay_scale, *m_notification_scale; @@ -95,6 +100,7 @@ private: void OnAudioDeviceSelected(wxCommandEvent& event); void OnAudioChannelsSelected(wxCommandEvent& event); void OnGraphicAPISelected(wxCommandEvent& event); + void OnUserDisplaySRGBSelected(wxCommandEvent& event); void OnAddPathClicked(wxCommandEvent& event); void OnRemovePathClicked(wxCommandEvent& event); void OnActiveAccountChanged(wxCommandEvent& event); @@ -115,6 +121,7 @@ private: void UpdateAccountInformation(); void UpdateOnlineAccounts(); void HandleGraphicsApiSelection(); + void HandleGammaSettings(); void ApplyConfig(); };