GX2: Implement GX2SetTVGamma and GX2SetDRCGamma (#1682)

This commit is contained in:
goeiecool9999 2025-11-14 13:51:58 +01:00 committed by GitHub
parent 4fa0df6dcf
commit 5390f9338c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 239 additions and 46 deletions

View file

@ -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)

View file

@ -120,16 +120,6 @@ uint8* LatteTextureLoader_getInputLinearOptimized(LatteTextureLoaderCtx* texture
#define LatteTextureLoader_getInputLinearOptimized_(__textureLoader,__x,__y,__stepX,__stepY,__bpp,__sliceIndex,__numSlices,__sample,__pitch,__height) (textureLoader->inputData+((__x/__stepX) + __pitch * (__y/__stepY) + (__sliceIndex + __numSlices * __sample) * __height * __pitch)*(__bpp/8))
float SRGB_to_RGB(float cs)
{
float cl;
if (cs <= 0.04045f)
cl = cs / 12.92f;
else
cl = powf(((cs + 0.055f) / 1.055f), 2.4f);
return cl;
}
void decodeBC1Block(uint8* inputData, float* output4x4RGBA)
{
// read colors

View file

@ -603,7 +603,7 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
shader_unbind(RendererShader::ShaderType::kGeometry);
shader_bind(shader->GetVertexShader());
shader_bind(shader->GetFragmentShader());
shader->SetUniformParameters(*texView, {imageWidth, imageHeight});
shader->SetUniformParameters(*texView, {imageWidth, imageHeight}, padView);
// set viewport
glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight);
@ -620,14 +620,12 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST);
texViewGL->samplerState.filterMag = 0xFFFFFFFF;
if ((!padView && !LatteGPUState.tvBufferUsesSRGB) || (padView && !LatteGPUState.drcBufferUsesSRGB))
glDisable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_FRAMEBUFFER_SRGB);
uint16 indexData[6] = { 0,1,2,3,4,5 };
glDrawRangeElements(GL_TRIANGLES, 0, 5, 6, GL_UNSIGNED_SHORT, indexData);
if ((!padView && !LatteGPUState.tvBufferUsesSRGB) || (padView && !LatteGPUState.drcBufferUsesSRGB))
glEnable(GL_FRAMEBUFFER_SRGB);
glEnable(GL_FRAMEBUFFER_SRGB);
// unbind texture
texture_bindAndActivate(nullptr, 0);

View file

@ -227,6 +227,16 @@ sint32 RendererShaderGL::GetUniformLocation(const char* name)
return glGetUniformLocation(m_program, name);
}
void RendererShaderGL::SetUniform1i(sint32 location, sint32 value)
{
glProgramUniform1i(m_program, location, value);
}
void RendererShaderGL::SetUniform1f(sint32 location, float value)
{
glProgramUniform1f(m_program, location, value);
}
void RendererShaderGL::SetUniform2fv(sint32 location, void* data, sint32 count)
{
glProgramUniform2fv(m_program, location, count, (const GLfloat*)data);

View file

@ -18,6 +18,9 @@ public:
GLuint GetShaderObject() const { cemu_assert_debug(m_isCompiled); return m_shader_object; }
sint32 GetUniformLocation(const char* name) override;
void SetUniform1i(sint32 location, sint32 value) override;
void SetUniform1f(sint32 location, float value) override;
void SetUniform2fv(sint32 location, void* data, sint32 count) override;
void SetUniform4iv(sint32 location, void* data, sint32 count) override;

View file

@ -1,9 +1,10 @@
#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"(
void main()
void outputShader()
{
colorOut0 = vec4(texture(textureSrc, passUV).rgb,1.0);
}
@ -49,7 +50,7 @@ vec4 bcFilter(vec2 uv, vec4 texelSize)
mix(sample1, sample0, sx), sy);
}
void main(){
void outputShader(){
vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy);
colorOut0 = vec4(bcFilter(passUV, texelSize).rgb,1.0);
}
@ -108,7 +109,7 @@ vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize)
return CubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y);
}
void main(){
void outputShader(){
vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy);
colorOut0 = vec4(BicubicHermiteTexture(passUV, texelSize), 1.0);
}
@ -135,14 +136,20 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con
m_uniformLocations[0].m_loc_textureSrcResolution = m_vertex_shader->GetUniformLocation("textureSrcResolution");
m_uniformLocations[0].m_loc_nativeResolution = m_vertex_shader->GetUniformLocation("nativeResolution");
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");
}
}
void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const
void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const
{
sint32 effectiveWidth, effectiveHeight;
texture_view.baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
@ -168,6 +175,22 @@ void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_
res[1] = (float)output_res.y;
shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1);
}
if (locations.m_loc_applySRGBEncoding != -1)
{
shader->SetUniform1i(locations.m_loc_applySRGBEncoding, padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB);
}
if (locations.m_loc_targetGamma != -1)
{
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);
}
};
setUniforms(m_vertex_shader.get(), m_uniformLocations[0]);
setUniforms(m_fragment_shader.get(), m_uniformLocations[1]);
@ -290,16 +313,52 @@ layout(push_constant) uniform pc {
vec2 textureSrcResolution;
vec2 nativeResolution;
vec2 outputResolution;
bool applySRGBEncoding; // true = app requested sRGB encoding
float targetGamma;
float displayGamma;
};
#else
uniform vec2 textureSrcResolution;
uniform vec2 nativeResolution;
uniform vec2 outputResolution;
uniform bool applySRGBEncoding;
uniform float targetGamma;
uniform float displayGamma;
#endif
layout(location = 0) smooth in vec2 passUV;
layout(binding = 0) uniform sampler2D textureSrc;
layout(location = 0) out vec4 colorOut0;
float sRGBEncode(float linear)
{
if(linear <= 0.0031308)
return 12.92f * linear;
else
return 1.055f * pow(linear, 1.0f / 2.4f) - 0.055f;
}
vec3 sRGBEncode(vec3 linear)
{
return vec3(sRGBEncode(linear.r), sRGBEncode(linear.g), sRGBEncode(linear.b));
}
// fwd. declaration
void outputShader();
void main()
{
outputShader(); // sets colorOut0
if(applySRGBEncoding)
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);
}
)" + shaderSrc;
}
void RendererOutputShader::InitializeStatic()

View file

@ -17,7 +17,7 @@ public:
RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source);
virtual ~RendererOutputShader() = default;
void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const;
void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const;
RendererShader* GetVertexShader() const
{
@ -55,6 +55,9 @@ protected:
sint32 m_loc_textureSrcResolution = -1;
sint32 m_loc_nativeResolution = -1;
sint32 m_loc_outputResolution = -1;
sint32 m_loc_applySRGBEncoding = -1;
sint32 m_loc_targetGamma = -1;
sint32 m_loc_displayGamma = -1;
} m_uniformLocations[2]{};
private:

View file

@ -20,6 +20,8 @@ public:
virtual sint32 GetUniformLocation(const char* name) = 0;
virtual void SetUniform1i(sint32 location, sint32 value) = 0;
virtual void SetUniform1f(sint32 location, float value) = 0;
virtual void SetUniform2fv(sint32 location, void* data, sint32 count) = 0;
virtual void SetUniform4iv(sint32 location, void* data, sint32 count) = 0;

View file

@ -232,6 +232,16 @@ sint32 RendererShaderVk::GetUniformLocation(const char* name)
return 0;
}
void RendererShaderVk::SetUniform1i(sint32 location, sint32 value)
{
cemu_assert_suspicious();
}
void RendererShaderVk::SetUniform1f(sint32 location, float value)
{
cemu_assert_suspicious();
}
void RendererShaderVk::SetUniform2fv(sint32 location, void* data, sint32 count)
{
cemu_assert_suspicious();

View file

@ -32,6 +32,8 @@ public:
static void Shutdown();
sint32 GetUniformLocation(const char* name) override;
void SetUniform1i(sint32 location, sint32 value) override;
void SetUniform1f(sint32 location, float value) override;
void SetUniform2fv(sint32 location, void* data, sint32 count) override;
void SetUniform4iv(sint32 location, void* data, sint32 count) override;
VkShaderModule& GetShaderModule() { return m_shader_module; }

View file

@ -319,18 +319,8 @@ VkSurfaceFormatKHR SwapchainInfoVk::ChooseSurfaceFormat(const std::vector<VkSurf
for (const auto& format : formats)
{
bool useSRGB = mainWindow ? LatteGPUState.tvBufferUsesSRGB : LatteGPUState.drcBufferUsesSRGB;
if (useSRGB)
{
if (format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
return format;
}
else
{
if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
return format;
}
if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
return format;
}
return formats[0];

View file

@ -59,7 +59,6 @@ struct SwapchainInfoVk
bool mainWindow{};
bool m_shouldRecreate = false;
bool m_usesSRGB = false;
VSync m_vsyncState = VSync::Immediate;
bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state

View file

@ -2590,7 +2590,6 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
uint64 hash = 0;
hash += (uint64)vertexRendererShader;
hash += (uint64)fragmentRendererShader;
hash += (uint64)(chainInfo.m_usesSRGB);
hash += ((uint64)padView) << 1;
const auto it = m_backbufferBlitPipelineCache.find(hash);
@ -2660,6 +2659,8 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
.offset = 0,
.size = 3 * sizeof(float) * 2 // 3 vec2's
+ 4 // + 1 VkBool32
+ 4 * 2 // + 2 float
};
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
@ -2771,10 +2772,6 @@ bool VulkanRenderer::UpdateSwapchainProperties(bool mainWindow)
if(chainInfo.m_vsyncState != configValue)
stateChanged = true;
const bool latteBufferUsesSRGB = mainWindow ? LatteGPUState.tvBufferUsesSRGB : LatteGPUState.drcBufferUsesSRGB;
if (chainInfo.m_usesSRGB != latteBufferUsesSRGB)
stateChanged = true;
int width, height;
if (mainWindow)
WindowSystem::GetWindowPhysSize(width, height);
@ -2799,7 +2796,6 @@ bool VulkanRenderer::UpdateSwapchainProperties(bool mainWindow)
chainInfo.m_shouldRecreate = false;
chainInfo.m_vsyncState = configValue;
chainInfo.m_usesSRGB = latteBufferUsesSRGB;
return true;
}
@ -3046,24 +3042,35 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr);
// update push constants
Vector2f pushData[3];
struct
{
Vector2f vecs[3];
VkBool32 applySRGBEncoding;
float targetGamma;
float displayGamma;
} pushData;
// textureSrcResolution
sint32 effectiveWidth, effectiveHeight;
texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
pushData[0] = {(float)effectiveWidth, (float)effectiveHeight};
pushData.vecs[0] = {(float)effectiveWidth, (float)effectiveHeight};
// nativeResolution
pushData[1] = {
pushData.vecs[1] = {
(float)texViewVk->baseTexture->width,
(float)texViewVk->baseTexture->height,
};
// outputResolution
pushData[2] = {(float)imageWidth,(float)imageHeight};
pushData.vecs[2] = {(float)imageWidth,(float)imageHeight};
vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float) * 2 * 3, &pushData);
pushData.applySRGBEncoding = padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB;
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);
vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0);

View file

@ -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);

View file

@ -116,6 +116,7 @@ GLFUNC(PFNGLPROGRAMUNIFORM1IPROC, glProgramUniform1i)
GLFUNC(PFNGLPROGRAMUNIFORM2IPROC, glProgramUniform2i)
GLFUNC(PFNGLPROGRAMUNIFORM1IVPROC, glProgramUniform1iv)
GLFUNC(PFNGLPROGRAMUNIFORM4IVPROC, glProgramUniform4iv)
GLFUNC(PFNGLPROGRAMUNIFORM1IPROC, glProgramUniform1f)
GLFUNC(PFNGLPROGRAMUNIFORM1FVPROC, glProgramUniform1fv)
GLFUNC(PFNGLPROGRAMUNIFORM2FVPROC, glProgramUniform2fv)

View file

@ -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;

View file

@ -96,6 +96,10 @@ public:
[[nodiscard]] static bool WaitForGX2DrawDoneEnabled();
[[nodiscard]] static GraphicAPI GetGraphicsAPI();
// gamma
[[nodiscard]] static float GetTVGamma();
[[nodiscard]] static float GetDRCGamma();
// audio
[[nodiscard]] static bool AudioOutputOnlyAux();
static void EnableAudioOnlyAux(bool state);

View file

@ -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);

View file

@ -389,6 +389,11 @@ struct CemuConfig
ConfigValue<bool> render_upside_down{ false };
ConfigValue<bool> async_compile{ true };
// Gamma
ConfigValue<bool> overrideAppGammaPreference{ false };
ConfigValue<float> overrideGammaValue{ 2.2f };
ConfigValue<float> userDisplayGamma { 2.2f }; // 0 = sRGB, >0 gamma
ConfigValue<bool> vk_accurate_barriers{ true };
struct

View file

@ -295,6 +295,10 @@ bool CemuApp::OnInit()
{
MSWEnableDarkMode(DarkMode_Always);
}
// extend tooltip duration to the maximum possible value
wxToolTip::SetDelay(-1);
wxToolTip::SetAutoPop(MAKELPARAM(std::numeric_limits<short>::max(),0));
#endif
for (auto&& path : failedWriteAccess)

View file

@ -377,6 +377,55 @@ wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook)
graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
{
auto box = new wxStaticBox(graphics_panel, wxID_ANY, _("Gamma settings"));
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto row = new wxFlexGridSizer(0, 2, 0, 0);
row->SetFlexibleDirection(wxBOTH);
row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
auto targetGammaLabel = new wxStaticText(box, wxID_ANY, _("Target Gamma"));
row->Add(targetGammaLabel, 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);
auto targetGammaTooltip = _("The display gamma to reproduce\nIf you are unsure, set this to 2.2");
targetGammaLabel->SetToolTip(targetGammaTooltip);
m_overrideGammaValue->SetToolTip(targetGammaTooltip);
auto displayGammaLabel = new wxStaticText(box, wxID_ANY, _("Display Gamma"));
row->Add(displayGammaLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxBoxSizer* srgbCheckBoxSizer = new wxBoxSizer(wxHORIZONTAL);
row->Add(srgbCheckBoxSizer);
m_userDisplayGamma = new wxSpinCtrlDouble(box, wxID_ANY, "2.2f", wxDefaultPosition, {230, -1}, wxSP_ARROW_KEYS, 0.1f, 4.0f, 2.2f, 0.1f);
auto displayGammaTooltip = _("The gamma of your monitor\nIf you are unsure, set this to 2.2");
m_userDisplayGamma->SetToolTip(displayGammaTooltip);
displayGammaLabel->SetToolTip(displayGammaTooltip);
m_userDisplayisSRGB = new wxCheckBox(box, wxID_ANY, "sRGB", wxDefaultPosition, wxDefaultSize);
m_userDisplayisSRGB->SetToolTip(_("Select this if Cemu is being displayed using a piecewise sRGB gamma curve.\n"
"This is typically not the case so you can probably leave this unchecked.\n"
"Exceptions include HDR displays (with HDR enabled), calibrated SDR displays with Windows 11's Auto Color Management enabled, "
"or when using a display profile with a VCGT tag that targets piecewise sRGB.\n"
"When this box is selected Cemu will compensate for the piecewise curve to approximate the pure gamma curve of a TV.\n"
"Colors will be more accurate, especially in dark scenes, but this may result in banding or crushed shadows, "
"so it is best if you display Cemu with pure gamma and do not use this setting."));
m_userDisplayisSRGB->Bind(wxEVT_CHECKBOX, &GeneralSettings2::OnUserDisplaySRGBSelected, this);
srgbCheckBoxSizer->Add(m_userDisplayGamma, 0, wxALL, 5);
srgbCheckBoxSizer->Add(m_userDisplayisSRGB, 0, wxALL | wxALIGN_CENTER_VERTICAL, 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 title's gamma preference"));
row->Add(m_overrideGamma, 0, wxALL, 5);
box_sizer->Add(row, 0, wxEXPAND, 5);
graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
{
wxString choices[] = { _("Bilinear"), _("Bicubic"), _("Hermite"), _("Nearest Neighbor") };
m_upscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Upscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS);
@ -1104,6 +1153,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();
@ -1681,6 +1733,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);
@ -2037,6 +2098,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);

View file

@ -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);