Skip to content

Commit

Permalink
Merge pull request #11850 from Filoppi/post_process_fixes
Browse files Browse the repository at this point in the history
Video: implement color correction to match the Wii/GC NTSC/PAL color spaces (and gamma)
  • Loading branch information
AdmiralCurtiss committed Jun 23, 2023
2 parents 5bf3d55 + daddf4c commit 02909bd
Show file tree
Hide file tree
Showing 34 changed files with 1,017 additions and 143 deletions.
86 changes: 86 additions & 0 deletions Data/Sys/Shaders/default_pre_post_process.glsl
@@ -0,0 +1,86 @@
// References:
// https://www.unravel.com.au/understanding-color-spaces

// SMPTE 170M - BT.601 (NTSC-M) -> BT.709
mat3 from_NTSCM = transpose(mat3(
0.939497225737661, 0.0502268452914346, 0.0102759289709032,
0.0177558637510127, 0.965824605885027, 0.0164195303639603,
-0.00162163209967010, -0.00437400622653655, 1.00599563832621));

// ARIB TR-B9 (9300K+27MPCD with chromatic adaptation) (NTSC-J) -> BT.709
mat3 from_NTSCJ = transpose(mat3(
0.823613036967492, -0.0943227111084757, 0.00799341532931119,
0.0289258355537324, 1.02310733489462, 0.00243547111576797,
-0.00569501554980891, 0.0161828357559315, 1.22328453915712));

// EBU - BT.470BG/BT.601 (PAL) -> BT.709
mat3 from_PAL = transpose(mat3(
1.04408168421813, -0.0440816842181253, 0.000000000000000,
0.000000000000000, 1.00000000000000, 0.000000000000000,
0.000000000000000, 0.0118044782106489, 0.988195521789351));

float3 LinearTosRGBGamma(float3 color)
{
float a = 0.055;

for (int i = 0; i < 3; ++i)
{
float x = color[i];
if (x <= 0.0031308)
x = x * 12.92;
else
x = (1.0 + a) * pow(x, 1.0 / 2.4) - a;
color[i] = x;
}

return color;
}

void main()
{
// Note: sampling in gamma space is "wrong" if the source
// and target resolution don't match exactly.
// Fortunately at the moment here they always should but to do this correctly,
// we'd need to sample from 4 pixels, de-apply the gamma from each of these,
// and then do linear sampling on their corrected value.
float4 color = Sample();

// Convert to linear space to do any other kind of operation
color.rgb = pow(color.rgb, game_gamma.xxx);

if (OptionEnabled(correct_color_space))
{
if (game_color_space == 0)
color.rgb = color.rgb * from_NTSCM;
else if (game_color_space == 1)
color.rgb = color.rgb * from_NTSCJ;
else if (game_color_space == 2)
color.rgb = color.rgb * from_PAL;
}

if (OptionEnabled(hdr_output))
{
const float hdr_paper_white = hdr_paper_white_nits / hdr_sdr_white_nits;
color.rgb *= hdr_paper_white;
}

if (OptionEnabled(linear_space_output))
{
// Nothing to do here
}
// Correct the SDR gamma for sRGB (PC/Monitor) or ~2.2 (Common TV gamma)
else if (OptionEnabled(correct_gamma))
{
if (OptionEnabled(sdr_display_gamma_sRGB))
color.rgb = LinearTosRGBGamma(color.rgb);
else
color.rgb = pow(color.rgb, (1.0 / sdr_display_custom_gamma).xxx);
}
// Restore the original gamma without changes
else
{
color.rgb = pow(color.rgb, (1.0 / game_gamma).xxx);
}

SetOutput(color);
}
16 changes: 16 additions & 0 deletions Source/Core/Core/Config/GraphicsSettings.cpp
Expand Up @@ -128,6 +128,22 @@ const Info<bool> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION{
{System::GFX, "Enhancements", "ArbitraryMipmapDetection"}, true};
const Info<float> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION_THRESHOLD{
{System::GFX, "Enhancements", "ArbitraryMipmapDetectionThreshold"}, 14.0f};
const Info<bool> GFX_ENHANCE_HDR_OUTPUT{{System::GFX, "Enhancements", "HDROutput"}, false};

// Color.Correction

const Info<bool> GFX_CC_CORRECT_COLOR_SPACE{{System::GFX, "ColorCorrection", "CorrectColorSpace"},
false};
const Info<ColorCorrectionRegion> GFX_CC_GAME_COLOR_SPACE{
{System::GFX, "ColorCorrection", "GameColorSpace"}, ColorCorrectionRegion::SMPTE_NTSCM};
const Info<bool> GFX_CC_CORRECT_GAMMA{{System::GFX, "ColorCorrection", "CorrectGamma"}, false};
const Info<float> GFX_CC_GAME_GAMMA{{System::GFX, "ColorCorrection", "GameGamma"}, 2.35f};
const Info<bool> GFX_CC_SDR_DISPLAY_GAMMA_SRGB{
{System::GFX, "ColorCorrection", "SDRDisplayGammaSRGB"}, true};
const Info<float> GFX_CC_SDR_DISPLAY_CUSTOM_GAMMA{
{System::GFX, "ColorCorrection", "SDRDisplayCustomGamma"}, 2.2f};
const Info<float> GFX_CC_HDR_PAPER_WHITE_NITS{{System::GFX, "ColorCorrection", "HDRPaperWhiteNits"},
200.f};

// Graphics.Stereoscopy

Expand Down
21 changes: 21 additions & 0 deletions Source/Core/Core/Config/GraphicsSettings.h
Expand Up @@ -11,6 +11,7 @@ enum class AspectMode : int;
enum class ShaderCompilationMode : int;
enum class StereoMode : int;
enum class TextureFilteringMode : int;
enum class ColorCorrectionRegion : int;
enum class TriState : int;

namespace Config
Expand Down Expand Up @@ -105,6 +106,26 @@ extern const Info<bool> GFX_ENHANCE_FORCE_TRUE_COLOR;
extern const Info<bool> GFX_ENHANCE_DISABLE_COPY_FILTER;
extern const Info<bool> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION;
extern const Info<float> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION_THRESHOLD;
extern const Info<bool> GFX_ENHANCE_HDR_OUTPUT;

// Color.Correction

static constexpr float GFX_CC_GAME_GAMMA_MIN = 2.2f;
static constexpr float GFX_CC_GAME_GAMMA_MAX = 2.8f;

static constexpr float GFX_CC_DISPLAY_GAMMA_MIN = 2.2f;
static constexpr float GFX_CC_DISPLAY_GAMMA_MAX = 2.4f;

static constexpr float GFX_CC_HDR_PAPER_WHITE_NITS_MIN = 80.f;
static constexpr float GFX_CC_HDR_PAPER_WHITE_NITS_MAX = 400.f;

extern const Info<bool> GFX_CC_CORRECT_COLOR_SPACE;
extern const Info<ColorCorrectionRegion> GFX_CC_GAME_COLOR_SPACE;
extern const Info<bool> GFX_CC_CORRECT_GAMMA;
extern const Info<float> GFX_CC_GAME_GAMMA;
extern const Info<bool> GFX_CC_SDR_DISPLAY_GAMMA_SRGB;
extern const Info<float> GFX_CC_SDR_DISPLAY_CUSTOM_GAMMA;
extern const Info<float> GFX_CC_HDR_PAPER_WHITE_NITS;

// Graphics.Stereoscopy

Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/DolphinAnalytics.cpp
Expand Up @@ -380,6 +380,7 @@ void DolphinAnalytics::MakePerGameBuilder()
builder.AddData("cfg-gfx-internal-resolution", g_Config.iEFBScale);
builder.AddData("cfg-gfx-tc-samples", g_Config.iSafeTextureCache_ColorSamples);
builder.AddData("cfg-gfx-stereo-mode", static_cast<int>(g_Config.stereo_mode));
builder.AddData("cfg-gfx-hdr", static_cast<int>(g_Config.bHDR));
builder.AddData("cfg-gfx-per-pixel-lighting", g_Config.bEnablePixelLighting);
builder.AddData("cfg-gfx-shader-compilation-mode", GetShaderCompilationMode(g_Config));
builder.AddData("cfg-gfx-wait-for-shaders", g_Config.bWaitForShadersBeforeStarting);
Expand Down
2 changes: 2 additions & 0 deletions Source/Core/DolphinQt/CMakeLists.txt
Expand Up @@ -87,6 +87,8 @@ add_executable(dolphin-emu
Config/Graphics/GraphicsWindow.h
Config/Graphics/HacksWidget.cpp
Config/Graphics/HacksWidget.h
Config/Graphics/ColorCorrectionConfigWindow.cpp
Config/Graphics/ColorCorrectionConfigWindow.h
Config/Graphics/PostProcessingConfigWindow.cpp
Config/Graphics/PostProcessingConfigWindow.h
Config/GraphicsModListWidget.cpp
Expand Down
181 changes: 181 additions & 0 deletions Source/Core/DolphinQt/Config/Graphics/ColorCorrectionConfigWindow.cpp
@@ -0,0 +1,181 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "DolphinQt/Config/Graphics/ColorCorrectionConfigWindow.h"

#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>

#include "Core/Config/GraphicsSettings.h"

#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
#include "DolphinQt/Config/ConfigControls/ConfigChoice.h"
#include "DolphinQt/Config/ConfigControls/ConfigFloatSlider.h"

#include "VideoCommon/VideoConfig.h"

ColorCorrectionConfigWindow::ColorCorrectionConfigWindow(QWidget* parent) : QDialog(parent)
{
setWindowTitle(tr("Color Correction Configuration"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

Create();
ConnectWidgets();
}

void ColorCorrectionConfigWindow::Create()
{
static const char TR_COLOR_SPACE_CORRECTION_DESCRIPTION[] = QT_TR_NOOP(
"Converts the colors to the color spaces that GC/Wii were meant to work with to sRGB/Rec.709."
"<br><br>There's no way of knowing what exact color space games were meant for,"
"<br>given there were multiple standards and most games didn't acknowledge them,"
"<br>so it's not correct to assume a format from the game disc region."
"<br>Just pick the one that looks more natural to you,"
" or match it with the region the game was developed in."
"<br><br>HDR output is required to show all the colors from the PAL and NTSC-J color spaces."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static const char TR_GAME_GAMMA_DESCRIPTION[] =
QT_TR_NOOP("NTSC-M and NTSC-J target gamma ~2.2. PAL targets gamma ~2.8."
"<br>None of the two were necessarily followed by games or TVs. 2.35 is a good "
"generic value for all regions."
"<br>If a game allows you to chose a gamma value, match it here.");
static const char TR_GAMMA_CORRECTION_DESCRIPTION[] = QT_TR_NOOP(
"Converts the gamma from what the game targeted to what your current SDR display targets."
"<br>Monitors often target sRGB. TVs often target 2.2."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");

// Color Space:

auto* const color_space_box = new QGroupBox(tr("Color Space"));
auto* const color_space_layout = new QGridLayout();
color_space_layout->setVerticalSpacing(7);
color_space_layout->setColumnStretch(1, 1);
color_space_box->setLayout(color_space_layout);

m_correct_color_space =
new ConfigBool(tr("Correct Color Space"), Config::GFX_CC_CORRECT_COLOR_SPACE);
color_space_layout->addWidget(m_correct_color_space, 0, 0);
m_correct_color_space->SetDescription(tr(TR_COLOR_SPACE_CORRECTION_DESCRIPTION));

// "ColorCorrectionRegion"
const QStringList game_color_space_enum{tr("NTSC-M (SMPTE 170M)"), tr("NTSC-J (ARIB TR-B9)"),
tr("PAL (EBU)")};

m_game_color_space = new ConfigChoice(game_color_space_enum, Config::GFX_CC_GAME_COLOR_SPACE);
color_space_layout->addWidget(new QLabel(tr("Game Color Space")), 1, 0);
color_space_layout->addWidget(m_game_color_space, 1, 1);

m_game_color_space->setEnabled(m_correct_color_space->isChecked());

// Gamma:

auto* const gamma_box = new QGroupBox(tr("Gamma"));
auto* const gamma_layout = new QGridLayout();
gamma_layout->setVerticalSpacing(7);
gamma_layout->setColumnStretch(1, 1);
gamma_box->setLayout(gamma_layout);

m_game_gamma = new ConfigFloatSlider(Config::GFX_CC_GAME_GAMMA_MIN, Config::GFX_CC_GAME_GAMMA_MAX,
Config::GFX_CC_GAME_GAMMA, 0.01f);
gamma_layout->addWidget(new QLabel(tr("Game Gamma")), 0, 0);
gamma_layout->addWidget(m_game_gamma, 0, 1);
m_game_gamma->SetDescription(tr(TR_GAME_GAMMA_DESCRIPTION));
m_game_gamma_value = new QLabel(tr(""));
gamma_layout->addWidget(m_game_gamma_value, 0, 2);

m_correct_gamma = new ConfigBool(tr("Correct SDR Gamma"), Config::GFX_CC_CORRECT_GAMMA);
gamma_layout->addWidget(m_correct_gamma, 1, 0);
m_correct_gamma->SetDescription(tr(TR_GAMMA_CORRECTION_DESCRIPTION));

m_sdr_display_gamma_srgb =
new ConfigBool(tr("SDR Display Gamma sRGB"), Config::GFX_CC_SDR_DISPLAY_GAMMA_SRGB);
gamma_layout->addWidget(m_sdr_display_gamma_srgb, 2, 0);

m_sdr_display_custom_gamma =
new ConfigFloatSlider(Config::GFX_CC_DISPLAY_GAMMA_MIN, Config::GFX_CC_DISPLAY_GAMMA_MAX,
Config::GFX_CC_SDR_DISPLAY_CUSTOM_GAMMA, 0.01f);
gamma_layout->addWidget(new QLabel(tr("SDR Display Custom Gamma")), 3, 0);
gamma_layout->addWidget(m_sdr_display_custom_gamma, 3, 1);
m_sdr_display_custom_gamma_value = new QLabel(tr(""));
gamma_layout->addWidget(m_sdr_display_custom_gamma_value, 3, 2);

m_sdr_display_gamma_srgb->setEnabled(m_correct_gamma->isChecked());
m_sdr_display_custom_gamma->setEnabled(m_correct_gamma->isChecked() &&
!m_sdr_display_gamma_srgb->isChecked());
m_game_gamma_value->setText(QString::asprintf("%f", m_game_gamma->GetValue()));
m_sdr_display_custom_gamma_value->setText(
QString::asprintf("%f", m_sdr_display_custom_gamma->GetValue()));

// HDR:

auto* const hdr_box = new QGroupBox(tr("HDR"));
auto* const hdr_layout = new QGridLayout();
hdr_layout->setVerticalSpacing(7);
hdr_layout->setColumnStretch(1, 1);
hdr_box->setLayout(hdr_layout);

m_hdr_paper_white_nits = new ConfigFloatSlider(Config::GFX_CC_HDR_PAPER_WHITE_NITS_MIN,
Config::GFX_CC_HDR_PAPER_WHITE_NITS_MAX,
Config::GFX_CC_HDR_PAPER_WHITE_NITS, 1.f);
hdr_layout->addWidget(new QLabel(tr("HDR Paper White Nits")), 0, 0);
hdr_layout->addWidget(m_hdr_paper_white_nits, 0, 1);
m_hdr_paper_white_nits_value = new QLabel(tr(""));
hdr_layout->addWidget(m_hdr_paper_white_nits_value, 0, 2);

m_hdr_paper_white_nits_value->setText(
QString::asprintf("%f", m_hdr_paper_white_nits->GetValue()));

// Other:

m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);

auto* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setAlignment(Qt::AlignTop);
layout->addWidget(color_space_box);
layout->addWidget(gamma_box);
layout->addWidget(hdr_box);
layout->addWidget(m_button_box);
setLayout(layout);
}

void ColorCorrectionConfigWindow::ConnectWidgets()
{
connect(m_correct_color_space, &QCheckBox::toggled, this,
[this] { m_game_color_space->setEnabled(m_correct_color_space->isChecked()); });

connect(m_game_gamma, &ConfigFloatSlider::valueChanged, this, [this] {
m_game_gamma_value->setText(QString::asprintf("%f", m_game_gamma->GetValue()));
});

connect(m_correct_gamma, &QCheckBox::toggled, this, [this] {
// The "m_game_gamma" shouldn't be grayed out as it can still affect the color space correction

// For the moment we leave this enabled even when we are outputting in HDR
// (which means they'd have no influence on the final image),
// mostly because we don't have a simple way to determine if HDR is engaged from here
m_sdr_display_gamma_srgb->setEnabled(m_correct_gamma->isChecked());
m_sdr_display_custom_gamma->setEnabled(m_correct_gamma->isChecked() &&
!m_sdr_display_gamma_srgb->isChecked());
});

connect(m_sdr_display_gamma_srgb, &QCheckBox::toggled, this, [this] {
m_sdr_display_custom_gamma->setEnabled(m_correct_gamma->isChecked() &&
!m_sdr_display_gamma_srgb->isChecked());
});

connect(m_sdr_display_custom_gamma, &ConfigFloatSlider::valueChanged, this, [this] {
m_sdr_display_custom_gamma_value->setText(
QString::asprintf("%f", m_sdr_display_custom_gamma->GetValue()));
});

connect(m_hdr_paper_white_nits, &ConfigFloatSlider::valueChanged, this, [this] {
m_hdr_paper_white_nits_value->setText(
QString::asprintf("%f", m_hdr_paper_white_nits->GetValue()));
});

connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
@@ -0,0 +1,36 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <QDialog>

class QWidget;
class QLabel;
class ConfigBool;
class ConfigChoice;
class ConfigFloatSlider;
class QDialogButtonBox;

class ColorCorrectionConfigWindow final : public QDialog
{
Q_OBJECT
public:
explicit ColorCorrectionConfigWindow(QWidget* parent);

private:
void Create();
void ConnectWidgets();

ConfigBool* m_correct_color_space;
ConfigChoice* m_game_color_space;
ConfigFloatSlider* m_game_gamma;
QLabel* m_game_gamma_value;
ConfigBool* m_correct_gamma;
ConfigBool* m_sdr_display_gamma_srgb;
ConfigFloatSlider* m_sdr_display_custom_gamma;
QLabel* m_sdr_display_custom_gamma_value;
ConfigFloatSlider* m_hdr_paper_white_nits;
QLabel* m_hdr_paper_white_nits_value;
QDialogButtonBox* m_button_box;
};

0 comments on commit 02909bd

Please sign in to comment.