Merge pull request #11850 from Filoppi/post_process_fixes

Video: implement color correction to match the Wii/GC NTSC/PAL color spaces (and gamma)
This commit is contained in:
Admiral H. Curtiss 2023-06-23 18:08:23 +02:00 committed by GitHub
commit 02909bd1a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1016 additions and 142 deletions

View File

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

View File

@ -128,6 +128,22 @@ const Info<bool> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION{
{System::GFX, "Enhancements", "ArbitraryMipmapDetection"}, true}; {System::GFX, "Enhancements", "ArbitraryMipmapDetection"}, true};
const Info<float> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION_THRESHOLD{ const Info<float> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION_THRESHOLD{
{System::GFX, "Enhancements", "ArbitraryMipmapDetectionThreshold"}, 14.0f}; {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 // Graphics.Stereoscopy

View File

@ -11,6 +11,7 @@ enum class AspectMode : int;
enum class ShaderCompilationMode : int; enum class ShaderCompilationMode : int;
enum class StereoMode : int; enum class StereoMode : int;
enum class TextureFilteringMode : int; enum class TextureFilteringMode : int;
enum class ColorCorrectionRegion : int;
enum class TriState : int; enum class TriState : int;
namespace Config namespace Config
@ -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_DISABLE_COPY_FILTER;
extern const Info<bool> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION; extern const Info<bool> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION;
extern const Info<float> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION_THRESHOLD; 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 // Graphics.Stereoscopy

View File

@ -380,6 +380,7 @@ void DolphinAnalytics::MakePerGameBuilder()
builder.AddData("cfg-gfx-internal-resolution", g_Config.iEFBScale); builder.AddData("cfg-gfx-internal-resolution", g_Config.iEFBScale);
builder.AddData("cfg-gfx-tc-samples", g_Config.iSafeTextureCache_ColorSamples); 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-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-per-pixel-lighting", g_Config.bEnablePixelLighting);
builder.AddData("cfg-gfx-shader-compilation-mode", GetShaderCompilationMode(g_Config)); builder.AddData("cfg-gfx-shader-compilation-mode", GetShaderCompilationMode(g_Config));
builder.AddData("cfg-gfx-wait-for-shaders", g_Config.bWaitForShadersBeforeStarting); builder.AddData("cfg-gfx-wait-for-shaders", g_Config.bWaitForShadersBeforeStarting);

View File

@ -87,6 +87,8 @@ add_executable(dolphin-emu
Config/Graphics/GraphicsWindow.h Config/Graphics/GraphicsWindow.h
Config/Graphics/HacksWidget.cpp Config/Graphics/HacksWidget.cpp
Config/Graphics/HacksWidget.h Config/Graphics/HacksWidget.h
Config/Graphics/ColorCorrectionConfigWindow.cpp
Config/Graphics/ColorCorrectionConfigWindow.h
Config/Graphics/PostProcessingConfigWindow.cpp Config/Graphics/PostProcessingConfigWindow.cpp
Config/Graphics/PostProcessingConfigWindow.h Config/Graphics/PostProcessingConfigWindow.h
Config/GraphicsModListWidget.cpp Config/GraphicsModListWidget.cpp

View File

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

View File

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

View File

@ -18,6 +18,7 @@
#include "DolphinQt/Config/ConfigControls/ConfigChoice.h" #include "DolphinQt/Config/ConfigControls/ConfigChoice.h"
#include "DolphinQt/Config/ConfigControls/ConfigRadio.h" #include "DolphinQt/Config/ConfigControls/ConfigRadio.h"
#include "DolphinQt/Config/ConfigControls/ConfigSlider.h" #include "DolphinQt/Config/ConfigControls/ConfigSlider.h"
#include "DolphinQt/Config/Graphics/ColorCorrectionConfigWindow.h"
#include "DolphinQt/Config/Graphics/GraphicsWindow.h" #include "DolphinQt/Config/Graphics/GraphicsWindow.h"
#include "DolphinQt/Config/Graphics/PostProcessingConfigWindow.h" #include "DolphinQt/Config/Graphics/PostProcessingConfigWindow.h"
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h" #include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
@ -102,6 +103,8 @@ void EnhancementsWidget::CreateWidgets()
m_texture_filtering_combo->addItem(tr("Force Linear and 16x Anisotropic"), m_texture_filtering_combo->addItem(tr("Force Linear and 16x Anisotropic"),
TEXTURE_FILTERING_FORCE_LINEAR_ANISO_16X); TEXTURE_FILTERING_FORCE_LINEAR_ANISO_16X);
m_configure_color_correction = new NonDefaultQPushButton(tr("Configure"));
m_pp_effect = new ToolTipComboBox(); m_pp_effect = new ToolTipComboBox();
m_configure_pp_effect = new NonDefaultQPushButton(tr("Configure")); m_configure_pp_effect = new NonDefaultQPushButton(tr("Configure"));
m_scaled_efb_copy = new ConfigBool(tr("Scaled EFB Copy"), Config::GFX_HACK_COPY_EFB_SCALED); m_scaled_efb_copy = new ConfigBool(tr("Scaled EFB Copy"), Config::GFX_HACK_COPY_EFB_SCALED);
@ -116,6 +119,7 @@ void EnhancementsWidget::CreateWidgets()
new ConfigBool(tr("Disable Copy Filter"), Config::GFX_ENHANCE_DISABLE_COPY_FILTER); new ConfigBool(tr("Disable Copy Filter"), Config::GFX_ENHANCE_DISABLE_COPY_FILTER);
m_arbitrary_mipmap_detection = new ConfigBool(tr("Arbitrary Mipmap Detection"), m_arbitrary_mipmap_detection = new ConfigBool(tr("Arbitrary Mipmap Detection"),
Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION); Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION);
m_hdr = new ConfigBool(tr("HDR Post-Processing"), Config::GFX_ENHANCE_HDR_OUTPUT);
int row = 0; int row = 0;
enhancements_layout->addWidget(new QLabel(tr("Internal Resolution:")), row, 0); enhancements_layout->addWidget(new QLabel(tr("Internal Resolution:")), row, 0);
@ -130,6 +134,10 @@ void EnhancementsWidget::CreateWidgets()
enhancements_layout->addWidget(m_texture_filtering_combo, row, 1, 1, -1); enhancements_layout->addWidget(m_texture_filtering_combo, row, 1, 1, -1);
++row; ++row;
enhancements_layout->addWidget(new QLabel(tr("Color Correction:")), row, 0);
enhancements_layout->addWidget(m_configure_color_correction, row, 1, 1, -1);
++row;
enhancements_layout->addWidget(new QLabel(tr("Post-Processing Effect:")), row, 0); enhancements_layout->addWidget(new QLabel(tr("Post-Processing Effect:")), row, 0);
enhancements_layout->addWidget(m_pp_effect, row, 1); enhancements_layout->addWidget(m_pp_effect, row, 1);
enhancements_layout->addWidget(m_configure_pp_effect, row, 2); enhancements_layout->addWidget(m_configure_pp_effect, row, 2);
@ -148,6 +156,7 @@ void EnhancementsWidget::CreateWidgets()
++row; ++row;
enhancements_layout->addWidget(m_disable_copy_filter, row, 0); enhancements_layout->addWidget(m_disable_copy_filter, row, 0);
enhancements_layout->addWidget(m_hdr, row, 1, 1, -1);
++row; ++row;
// Stereoscopy // Stereoscopy
@ -188,11 +197,14 @@ void EnhancementsWidget::ConnectWidgets()
[this](int) { SaveSettings(); }); [this](int) { SaveSettings(); });
connect(m_3d_mode, qOverload<int>(&QComboBox::currentIndexChanged), [this] { connect(m_3d_mode, qOverload<int>(&QComboBox::currentIndexChanged), [this] {
m_block_save = true; m_block_save = true;
m_configure_color_correction->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
LoadPPShaders(); LoadPPShaders();
m_block_save = false; m_block_save = false;
SaveSettings(); SaveSettings();
}); });
connect(m_configure_color_correction, &QPushButton::clicked, this,
&EnhancementsWidget::ConfigureColorCorrection);
connect(m_configure_pp_effect, &QPushButton::clicked, this, connect(m_configure_pp_effect, &QPushButton::clicked, this,
&EnhancementsWidget::ConfigurePostProcessingShader); &EnhancementsWidget::ConfigurePostProcessingShader);
} }
@ -311,6 +323,8 @@ void EnhancementsWidget::LoadSettings()
break; break;
} }
m_configure_color_correction->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
// Post Processing Shader // Post Processing Shader
LoadPPShaders(); LoadPPShaders();
@ -320,6 +334,9 @@ void EnhancementsWidget::LoadSettings()
m_3d_convergence->setEnabled(supports_stereoscopy); m_3d_convergence->setEnabled(supports_stereoscopy);
m_3d_depth->setEnabled(supports_stereoscopy); m_3d_depth->setEnabled(supports_stereoscopy);
m_3d_swap_eyes->setEnabled(supports_stereoscopy); m_3d_swap_eyes->setEnabled(supports_stereoscopy);
m_hdr->setEnabled(g_Config.backend_info.bSupportsHDROutput);
m_block_save = false; m_block_save = false;
} }
@ -436,6 +453,9 @@ void EnhancementsWidget::AddDescriptions()
"scaling filter selected by the game.<br><br>Any option except 'Default' will alter the look " "scaling filter selected by the game.<br><br>Any option except 'Default' will alter the look "
"of the game's textures and might cause issues in a small number of " "of the game's textures and might cause issues in a small number of "
"games.<br><br><dolphin_emphasis>If unsure, select 'Default'.</dolphin_emphasis>"); "games.<br><br><dolphin_emphasis>If unsure, select 'Default'.</dolphin_emphasis>");
static const char TR_COLOR_CORRECTION_DESCRIPTION[] =
QT_TR_NOOP("A group of features to make the colors more accurate,"
" matching the color space Wii and GC games were meant for.");
static const char TR_POSTPROCESSING_DESCRIPTION[] = static const char TR_POSTPROCESSING_DESCRIPTION[] =
QT_TR_NOOP("Applies a post-processing effect after rendering a frame.<br><br " QT_TR_NOOP("Applies a post-processing effect after rendering a frame.<br><br "
"/><dolphin_emphasis>If unsure, select (off).</dolphin_emphasis>"); "/><dolphin_emphasis>If unsure, select (off).</dolphin_emphasis>");
@ -498,6 +518,13 @@ void EnhancementsWidget::AddDescriptions()
"reduce stutter in games that frequently load new textures. This feature is not compatible " "reduce stutter in games that frequently load new textures. This feature is not compatible "
"with GPU Texture Decoding.<br><br><dolphin_emphasis>If unsure, leave this " "with GPU Texture Decoding.<br><br><dolphin_emphasis>If unsure, leave this "
"checked.</dolphin_emphasis>"); "checked.</dolphin_emphasis>");
static const char TR_HDR_DESCRIPTION[] = QT_TR_NOOP(
"Enables scRGB HDR output (if supported by your graphics backend and monitor)."
" Fullscreen might be required."
"<br><br>This gives post process shaders more room for accuracy, allows \"AutoHDR\" "
"post-process shaders to work, and allows to fully display the PAL and NTSC-J color spaces."
"<br><br>Note that games still render in SDR internally."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
m_ir_combo->SetTitle(tr("Internal Resolution")); m_ir_combo->SetTitle(tr("Internal Resolution"));
m_ir_combo->SetDescription(tr(TR_INTERNAL_RESOLUTION_DESCRIPTION)); m_ir_combo->SetDescription(tr(TR_INTERNAL_RESOLUTION_DESCRIPTION));
@ -508,6 +535,8 @@ void EnhancementsWidget::AddDescriptions()
m_texture_filtering_combo->SetTitle(tr("Texture Filtering")); m_texture_filtering_combo->SetTitle(tr("Texture Filtering"));
m_texture_filtering_combo->SetDescription(tr(TR_FORCE_TEXTURE_FILTERING_DESCRIPTION)); m_texture_filtering_combo->SetDescription(tr(TR_FORCE_TEXTURE_FILTERING_DESCRIPTION));
m_configure_color_correction->setToolTip(tr(TR_COLOR_CORRECTION_DESCRIPTION));
m_pp_effect->SetTitle(tr("Post-Processing Effect")); m_pp_effect->SetTitle(tr("Post-Processing Effect"));
m_pp_effect->SetDescription(tr(TR_POSTPROCESSING_DESCRIPTION)); m_pp_effect->SetDescription(tr(TR_POSTPROCESSING_DESCRIPTION));
@ -525,6 +554,8 @@ void EnhancementsWidget::AddDescriptions()
m_arbitrary_mipmap_detection->SetDescription(tr(TR_ARBITRARY_MIPMAP_DETECTION_DESCRIPTION)); m_arbitrary_mipmap_detection->SetDescription(tr(TR_ARBITRARY_MIPMAP_DETECTION_DESCRIPTION));
m_hdr->SetDescription(tr(TR_HDR_DESCRIPTION));
m_3d_mode->SetTitle(tr("Stereoscopic 3D Mode")); m_3d_mode->SetTitle(tr("Stereoscopic 3D Mode"));
m_3d_mode->SetDescription(tr(TR_3D_MODE_DESCRIPTION)); m_3d_mode->SetDescription(tr(TR_3D_MODE_DESCRIPTION));
@ -537,6 +568,11 @@ void EnhancementsWidget::AddDescriptions()
m_3d_swap_eyes->SetDescription(tr(TR_3D_SWAP_EYES_DESCRIPTION)); m_3d_swap_eyes->SetDescription(tr(TR_3D_SWAP_EYES_DESCRIPTION));
} }
void EnhancementsWidget::ConfigureColorCorrection()
{
ColorCorrectionConfigWindow(this).exec();
}
void EnhancementsWidget::ConfigurePostProcessingShader() void EnhancementsWidget::ConfigurePostProcessingShader()
{ {
const std::string shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER); const std::string shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER);

View File

@ -30,6 +30,7 @@ private:
void CreateWidgets(); void CreateWidgets();
void ConnectWidgets(); void ConnectWidgets();
void AddDescriptions(); void AddDescriptions();
void ConfigureColorCorrection();
void ConfigurePostProcessingShader(); void ConfigurePostProcessingShader();
void LoadPPShaders(); void LoadPPShaders();
@ -38,6 +39,7 @@ private:
ToolTipComboBox* m_aa_combo; ToolTipComboBox* m_aa_combo;
ToolTipComboBox* m_texture_filtering_combo; ToolTipComboBox* m_texture_filtering_combo;
ToolTipComboBox* m_pp_effect; ToolTipComboBox* m_pp_effect;
QPushButton* m_configure_color_correction;
QPushButton* m_configure_pp_effect; QPushButton* m_configure_pp_effect;
ConfigBool* m_scaled_efb_copy; ConfigBool* m_scaled_efb_copy;
ConfigBool* m_per_pixel_lighting; ConfigBool* m_per_pixel_lighting;
@ -46,6 +48,7 @@ private:
ConfigBool* m_force_24bit_color; ConfigBool* m_force_24bit_color;
ConfigBool* m_disable_copy_filter; ConfigBool* m_disable_copy_filter;
ConfigBool* m_arbitrary_mipmap_detection; ConfigBool* m_arbitrary_mipmap_detection;
ConfigBool* m_hdr;
// Stereoscopy // Stereoscopy
ConfigChoice* m_3d_mode; ConfigChoice* m_3d_mode;

View File

@ -80,6 +80,7 @@
<ClCompile Include="Config\Graphics\GeneralWidget.cpp" /> <ClCompile Include="Config\Graphics\GeneralWidget.cpp" />
<ClCompile Include="Config\Graphics\GraphicsWindow.cpp" /> <ClCompile Include="Config\Graphics\GraphicsWindow.cpp" />
<ClCompile Include="Config\Graphics\HacksWidget.cpp" /> <ClCompile Include="Config\Graphics\HacksWidget.cpp" />
<ClCompile Include="Config\Graphics\ColorCorrectionConfigWindow.cpp" />
<ClCompile Include="Config\Graphics\PostProcessingConfigWindow.cpp" /> <ClCompile Include="Config\Graphics\PostProcessingConfigWindow.cpp" />
<ClCompile Include="Config\GraphicsModListWidget.cpp" /> <ClCompile Include="Config\GraphicsModListWidget.cpp" />
<ClCompile Include="Config\GraphicsModWarningWidget.cpp" /> <ClCompile Include="Config\GraphicsModWarningWidget.cpp" />
@ -283,6 +284,7 @@
<QtMoc Include="Config\Graphics\GeneralWidget.h" /> <QtMoc Include="Config\Graphics\GeneralWidget.h" />
<QtMoc Include="Config\Graphics\GraphicsWindow.h" /> <QtMoc Include="Config\Graphics\GraphicsWindow.h" />
<QtMoc Include="Config\Graphics\HacksWidget.h" /> <QtMoc Include="Config\Graphics\HacksWidget.h" />
<QtMoc Include="Config\Graphics\ColorCorrectionConfigWindow.h" />
<QtMoc Include="Config\Graphics\PostProcessingConfigWindow.h" /> <QtMoc Include="Config\Graphics\PostProcessingConfigWindow.h" />
<QtMoc Include="Config\GraphicsModListWidget.h" /> <QtMoc Include="Config\GraphicsModListWidget.h" />
<QtMoc Include="Config\GraphicsModWarningWidget.h" /> <QtMoc Include="Config\GraphicsModWarningWidget.h" />

View File

@ -15,6 +15,7 @@
#include "VideoBackends/D3D/D3DState.h" #include "VideoBackends/D3D/D3DState.h"
#include "VideoBackends/D3D/DXTexture.h" #include "VideoBackends/D3D/DXTexture.h"
#include "VideoBackends/D3DCommon/D3DCommon.h" #include "VideoBackends/D3DCommon/D3DCommon.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/VideoConfig.h" #include "VideoCommon/VideoConfig.h"
namespace DX11 namespace DX11
@ -202,12 +203,14 @@ std::vector<u32> GetAAModes(u32 adapter_index)
if (temp_feature_level == D3D_FEATURE_LEVEL_10_0) if (temp_feature_level == D3D_FEATURE_LEVEL_10_0)
return {}; return {};
const DXGI_FORMAT target_format =
D3DCommon::GetDXGIFormatForAbstractFormat(FramebufferManager::GetEFBColorFormat(), false);
std::vector<u32> aa_modes; std::vector<u32> aa_modes;
for (u32 samples = 1; samples <= D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; ++samples) for (u32 samples = 1; samples <= D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; ++samples)
{ {
UINT quality_levels = 0; UINT quality_levels = 0;
if (SUCCEEDED(temp_device->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, samples, if (SUCCEEDED(
&quality_levels)) && temp_device->CheckMultisampleQualityLevels(target_format, samples, &quality_levels)) &&
quality_levels > 0) quality_levels > 0)
{ {
aa_modes.push_back(samples); aa_modes.push_back(samples);

View File

@ -175,6 +175,9 @@ void Gfx::OnConfigChanged(u32 bits)
// Quad-buffer changes require swap chain recreation. // Quad-buffer changes require swap chain recreation.
if (bits & CONFIG_CHANGE_BIT_STEREO_MODE && m_swap_chain) if (bits & CONFIG_CHANGE_BIT_STEREO_MODE && m_swap_chain)
m_swap_chain->SetStereo(SwapChain::WantsStereo()); m_swap_chain->SetStereo(SwapChain::WantsStereo());
if (bits & CONFIG_CHANGE_BIT_HDR && m_swap_chain)
m_swap_chain->SetHDR(SwapChain::WantsHDR());
} }
void Gfx::CheckForSwapChainChanges() void Gfx::CheckForSwapChainChanges()

View File

@ -114,6 +114,7 @@ void VideoBackend::FillBackendInfo()
g_Config.backend_info.bSupportsSettingObjectNames = true; g_Config.backend_info.bSupportsSettingObjectNames = true;
g_Config.backend_info.bSupportsPartialMultisampleResolve = true; g_Config.backend_info.bSupportsPartialMultisampleResolve = true;
g_Config.backend_info.bSupportsDynamicVertexLoader = false; g_Config.backend_info.bSupportsDynamicVertexLoader = false;
g_Config.backend_info.bSupportsHDROutput = true;
g_Config.backend_info.Adapters = D3DCommon::GetAdapterNames(); g_Config.backend_info.Adapters = D3DCommon::GetAdapterNames();
g_Config.backend_info.AAModes = D3D::GetAAModes(g_Config.iAdapter); g_Config.backend_info.AAModes = D3D::GetAAModes(g_Config.iAdapter);

View File

@ -21,7 +21,7 @@ std::unique_ptr<SwapChain> SwapChain::Create(const WindowSystemInfo& wsi)
{ {
std::unique_ptr<SwapChain> swap_chain = std::unique_ptr<SwapChain> swap_chain =
std::make_unique<SwapChain>(wsi, D3D::dxgi_factory.Get(), D3D::device.Get()); std::make_unique<SwapChain>(wsi, D3D::dxgi_factory.Get(), D3D::device.Get());
if (!swap_chain->CreateSwapChain(WantsStereo())) if (!swap_chain->CreateSwapChain(WantsStereo(), WantsHDR()))
return nullptr; return nullptr;
return swap_chain; return swap_chain;

View File

@ -421,6 +421,12 @@ void Gfx::OnConfigChanged(u32 bits)
m_swap_chain->SetStereo(SwapChain::WantsStereo()); m_swap_chain->SetStereo(SwapChain::WantsStereo());
} }
if (m_swap_chain && bits & CONFIG_CHANGE_BIT_HDR)
{
ExecuteCommandList(true);
m_swap_chain->SetHDR(SwapChain::WantsHDR());
}
// Wipe sampler cache if force texture filtering or anisotropy changes. // Wipe sampler cache if force texture filtering or anisotropy changes.
if (bits & (CONFIG_CHANGE_BIT_ANISOTROPY | CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING)) if (bits & (CONFIG_CHANGE_BIT_ANISOTROPY | CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING))
{ {

View File

@ -22,7 +22,7 @@ std::unique_ptr<SwapChain> SwapChain::Create(const WindowSystemInfo& wsi)
{ {
std::unique_ptr<SwapChain> swap_chain = std::make_unique<SwapChain>( std::unique_ptr<SwapChain> swap_chain = std::make_unique<SwapChain>(
wsi, g_dx_context->GetDXGIFactory(), g_dx_context->GetCommandQueue()); wsi, g_dx_context->GetDXGIFactory(), g_dx_context->GetCommandQueue());
if (!swap_chain->CreateSwapChain(WantsStereo())) if (!swap_chain->CreateSwapChain(WantsStereo(), WantsHDR()))
return nullptr; return nullptr;
return swap_chain; return swap_chain;

View File

@ -16,6 +16,7 @@
#include "VideoBackends/D3D12/Common.h" #include "VideoBackends/D3D12/Common.h"
#include "VideoBackends/D3D12/D3D12StreamBuffer.h" #include "VideoBackends/D3D12/D3D12StreamBuffer.h"
#include "VideoBackends/D3D12/DescriptorHeapManager.h" #include "VideoBackends/D3D12/DescriptorHeapManager.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/VideoConfig.h" #include "VideoCommon/VideoConfig.h"
namespace DX12 namespace DX12
@ -65,11 +66,13 @@ std::vector<u32> DXContext::GetAAModes(u32 adapter_index)
return {}; return {};
} }
const DXGI_FORMAT target_format =
D3DCommon::GetDXGIFormatForAbstractFormat(FramebufferManager::GetEFBColorFormat(), false);
std::vector<u32> aa_modes; std::vector<u32> aa_modes;
for (u32 samples = 1; samples < D3D12_MAX_MULTISAMPLE_SAMPLE_COUNT; ++samples) for (u32 samples = 1; samples < D3D12_MAX_MULTISAMPLE_SAMPLE_COUNT; ++samples)
{ {
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS multisample_quality_levels = {}; D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS multisample_quality_levels = {};
multisample_quality_levels.Format = DXGI_FORMAT_R8G8B8A8_UNORM; multisample_quality_levels.Format = target_format;
multisample_quality_levels.SampleCount = samples; multisample_quality_levels.SampleCount = samples;
temp_device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, temp_device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,

View File

@ -90,6 +90,7 @@ void VideoBackend::FillBackendInfo()
g_Config.backend_info.bSupportsPartialMultisampleResolve = true; g_Config.backend_info.bSupportsPartialMultisampleResolve = true;
g_Config.backend_info.bSupportsDynamicVertexLoader = true; g_Config.backend_info.bSupportsDynamicVertexLoader = true;
g_Config.backend_info.bSupportsVSLinePointExpand = true; g_Config.backend_info.bSupportsVSLinePointExpand = true;
g_Config.backend_info.bSupportsHDROutput = true;
// We can only check texture support once we have a device. // We can only check texture support once we have a device.
if (g_dx_context) if (g_dx_context)

View File

@ -51,13 +51,18 @@ bool SwapChain::WantsStereo()
return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer; return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer;
} }
bool SwapChain::WantsHDR()
{
return g_ActiveConfig.bHDR;
}
u32 SwapChain::GetSwapChainFlags() const u32 SwapChain::GetSwapChainFlags() const
{ {
// This flag is necessary if we want to use a flip-model swapchain without locking the framerate // This flag is necessary if we want to use a flip-model swapchain without locking the framerate
return m_allow_tearing_supported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; return m_allow_tearing_supported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
} }
bool SwapChain::CreateSwapChain(bool stereo) bool SwapChain::CreateSwapChain(bool stereo, bool hdr)
{ {
RECT client_rc; RECT client_rc;
if (GetClientRect(static_cast<HWND>(m_wsi.render_surface), &client_rc)) if (GetClientRect(static_cast<HWND>(m_wsi.render_surface), &client_rc))
@ -66,6 +71,9 @@ bool SwapChain::CreateSwapChain(bool stereo)
m_height = client_rc.bottom - client_rc.top; m_height = client_rc.bottom - client_rc.top;
} }
m_stereo = false;
m_hdr = false;
// Try using the Win8 version if available. // Try using the Win8 version if available.
Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory2; Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory2;
HRESULT hr = m_dxgi_factory.As(&dxgi_factory2); HRESULT hr = m_dxgi_factory.As(&dxgi_factory2);
@ -81,6 +89,7 @@ bool SwapChain::CreateSwapChain(bool stereo)
swap_chain_desc.SampleDesc.Count = 1; swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.SampleDesc.Quality = 0; swap_chain_desc.SampleDesc.Quality = 0;
swap_chain_desc.Format = GetDXGIFormatForAbstractFormat(m_texture_format, false); swap_chain_desc.Format = GetDXGIFormatForAbstractFormat(m_texture_format, false);
swap_chain_desc.Scaling = DXGI_SCALING_STRETCH; swap_chain_desc.Scaling = DXGI_SCALING_STRETCH;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swap_chain_desc.Stereo = stereo; swap_chain_desc.Stereo = stereo;
@ -108,6 +117,8 @@ bool SwapChain::CreateSwapChain(bool stereo)
// support the newer DXGI interface aren't going to support DX12 anyway. // support the newer DXGI interface aren't going to support DX12 anyway.
if (FAILED(hr)) if (FAILED(hr))
{ {
hdr = false;
DXGI_SWAP_CHAIN_DESC desc = {}; DXGI_SWAP_CHAIN_DESC desc = {};
desc.BufferDesc.Width = m_width; desc.BufferDesc.Width = m_width;
desc.BufferDesc.Height = m_height; desc.BufferDesc.Height = m_height;
@ -138,6 +149,37 @@ bool SwapChain::CreateSwapChain(bool stereo)
WARN_LOG_FMT(VIDEO, "MakeWindowAssociation() failed: {}", Common::HRWrap(hr)); WARN_LOG_FMT(VIDEO, "MakeWindowAssociation() failed: {}", Common::HRWrap(hr));
m_stereo = stereo; m_stereo = stereo;
if (hdr)
{
// Only try to activate HDR here, to avoid failing when creating the swapchain
// (we can't know if the format is supported upfront)
Microsoft::WRL::ComPtr<IDXGISwapChain4> swap_chain4;
hr = m_swap_chain->QueryInterface(IID_PPV_ARGS(&swap_chain4));
if (SUCCEEDED(hr))
{
UINT color_space_support = 0;
// Note that this should succeed even if HDR is not currently engaged on the monitor,
// but it should display fine nonetheless.
// We need to check for DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 as checking for
// scRGB always returns false (DX bug).
hr = swap_chain4->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020,
&color_space_support);
if (SUCCEEDED(hr) && (color_space_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT))
{
hr = swap_chain4->ResizeBuffers(SWAP_CHAIN_BUFFER_COUNT, 0, 0,
GetDXGIFormatForAbstractFormat(m_texture_format_hdr, false),
GetSwapChainFlags());
if (SUCCEEDED(hr))
{
hr = swap_chain4->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709);
if (SUCCEEDED(hr))
m_hdr = hdr;
}
}
}
}
if (!CreateSwapChainBuffers()) if (!CreateSwapChainBuffers())
{ {
PanicAlertFmt("Failed to create swap chain buffers"); PanicAlertFmt("Failed to create swap chain buffers");
@ -164,12 +206,19 @@ bool SwapChain::ResizeSwapChain()
{ {
DestroySwapChainBuffers(); DestroySwapChainBuffers();
HRESULT hr = m_swap_chain->ResizeBuffers(SWAP_CHAIN_BUFFER_COUNT, 0, 0, // The swap chain fills up the size of the window if no size is specified
GetDXGIFormatForAbstractFormat(m_texture_format, false), HRESULT hr = m_swap_chain->ResizeBuffers(SWAP_CHAIN_BUFFER_COUNT, 0, 0, DXGI_FORMAT_UNKNOWN,
GetSwapChainFlags()); GetSwapChainFlags());
if (FAILED(hr)) if (FAILED(hr))
WARN_LOG_FMT(VIDEO, "ResizeBuffers() failed: {}", Common::HRWrap(hr)); WARN_LOG_FMT(VIDEO, "ResizeBuffers() failed: {}", Common::HRWrap(hr));
Microsoft::WRL::ComPtr<IDXGISwapChain4> swap_chain4;
hr = m_swap_chain->QueryInterface(IID_PPV_ARGS(&swap_chain4));
if (SUCCEEDED(hr))
hr = swap_chain4->SetColorSpace1(m_hdr ? DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 :
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
DXGI_SWAP_CHAIN_DESC desc; DXGI_SWAP_CHAIN_DESC desc;
if (SUCCEEDED(m_swap_chain->GetDesc(&desc))) if (SUCCEEDED(m_swap_chain->GetDesc(&desc)))
{ {
@ -186,10 +235,28 @@ void SwapChain::SetStereo(bool stereo)
return; return;
DestroySwapChain(); DestroySwapChain();
if (!CreateSwapChain(stereo)) // Do not try to re-activate HDR here if it had already failed
if (!CreateSwapChain(stereo, m_hdr))
{ {
PanicAlertFmt("Failed to switch swap chain stereo mode"); PanicAlertFmt("Failed to switch swap chain stereo mode");
CreateSwapChain(false); CreateSwapChain(false, false);
}
}
void SwapChain::SetHDR(bool hdr)
{
if (m_hdr == hdr)
return;
// NOTE: as an optimization here we could just call "ResizeSwapChain()"
// by adding some code to check if we could change the format to HDR.
DestroySwapChain();
// Do not try to re-activate stereo mode here if it had already failed
if (!CreateSwapChain(m_stereo, hdr))
{
PanicAlertFmt("Failed to switch swap chain SDR/HDR mode");
CreateSwapChain(false, false);
} }
} }
@ -249,7 +316,8 @@ bool SwapChain::ChangeSurface(void* native_handle)
{ {
DestroySwapChain(); DestroySwapChain();
m_wsi.render_surface = native_handle; m_wsi.render_surface = native_handle;
return CreateSwapChain(m_stereo); // We only keep the swap chain settings (HDR/Stereo) that had successfully applied beofre
return CreateSwapChain(m_stereo, m_hdr);
} }
} // namespace D3DCommon } // namespace D3DCommon

View File

@ -25,8 +25,13 @@ public:
// Returns true if the stereo mode is quad-buffering. // Returns true if the stereo mode is quad-buffering.
static bool WantsStereo(); static bool WantsStereo();
static bool WantsHDR();
IDXGISwapChain* GetDXGISwapChain() const { return m_swap_chain.Get(); } IDXGISwapChain* GetDXGISwapChain() const { return m_swap_chain.Get(); }
AbstractTextureFormat GetFormat() const { return m_texture_format; } AbstractTextureFormat GetFormat() const
{
return m_hdr ? m_texture_format_hdr : m_texture_format;
}
u32 GetWidth() const { return m_width; } u32 GetWidth() const { return m_width; }
u32 GetHeight() const { return m_height; } u32 GetHeight() const { return m_height; }
@ -43,10 +48,11 @@ public:
bool ChangeSurface(void* native_handle); bool ChangeSurface(void* native_handle);
bool ResizeSwapChain(); bool ResizeSwapChain();
void SetStereo(bool stereo); void SetStereo(bool stereo);
void SetHDR(bool hdr);
protected: protected:
u32 GetSwapChainFlags() const; u32 GetSwapChainFlags() const;
bool CreateSwapChain(bool stereo); bool CreateSwapChain(bool stereo = false, bool hdr = false);
void DestroySwapChain(); void DestroySwapChain();
virtual bool CreateSwapChainBuffers() = 0; virtual bool CreateSwapChainBuffers() = 0;
@ -56,12 +62,14 @@ protected:
Microsoft::WRL::ComPtr<IDXGIFactory> m_dxgi_factory; Microsoft::WRL::ComPtr<IDXGIFactory> m_dxgi_factory;
Microsoft::WRL::ComPtr<IDXGISwapChain> m_swap_chain; Microsoft::WRL::ComPtr<IDXGISwapChain> m_swap_chain;
Microsoft::WRL::ComPtr<IUnknown> m_d3d_device; Microsoft::WRL::ComPtr<IUnknown> m_d3d_device;
AbstractTextureFormat m_texture_format = AbstractTextureFormat::RGBA8; const AbstractTextureFormat m_texture_format = AbstractTextureFormat::RGB10_A2;
const AbstractTextureFormat m_texture_format_hdr = AbstractTextureFormat::RGBA16F;
u32 m_width = 1; u32 m_width = 1;
u32 m_height = 1; u32 m_height = 1;
bool m_stereo = false; bool m_stereo = false;
bool m_hdr = false;
bool m_allow_tearing_supported = false; bool m_allow_tearing_supported = false;
bool m_has_fullscreen = false; bool m_has_fullscreen = false;
bool m_fullscreen_request = false; bool m_fullscreen_request = false;

View File

@ -399,7 +399,7 @@ void VKGfx::OnConfigChanged(u32 bits)
} }
// For quad-buffered stereo we need to change the layer count, so recreate the swap chain. // For quad-buffered stereo we need to change the layer count, so recreate the swap chain.
if (m_swap_chain && bits & CONFIG_CHANGE_BIT_STEREO_MODE) if (m_swap_chain && (bits & CONFIG_CHANGE_BIT_STEREO_MODE) || (bits & CONFIG_CHANGE_BIT_HDR))
{ {
ExecuteCommandBuffer(false, true); ExecuteCommandBuffer(false, true);
m_swap_chain->RecreateSwapChain(); m_swap_chain->RecreateSwapChain();

View File

@ -154,7 +154,7 @@ bool SwapChain::SelectSurfaceFormat()
&format_count, surface_formats.data()); &format_count, surface_formats.data());
ASSERT(res == VK_SUCCESS); ASSERT(res == VK_SUCCESS);
// If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA8
if (surface_formats[0].format == VK_FORMAT_UNDEFINED) if (surface_formats[0].format == VK_FORMAT_UNDEFINED)
{ {
m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM;
@ -162,22 +162,61 @@ bool SwapChain::SelectSurfaceFormat()
return true; return true;
} }
// Try to find a suitable format. const VkSurfaceFormatKHR* surface_format_RGBA8 = nullptr;
const VkSurfaceFormatKHR* surface_format_BGRA8 = nullptr;
const VkSurfaceFormatKHR* surface_format_RGB10_A2 = nullptr;
const VkSurfaceFormatKHR* surface_format_RGBA16F_scRGB = nullptr;
// Try to find all suitable formats.
for (const VkSurfaceFormatKHR& surface_format : surface_formats) for (const VkSurfaceFormatKHR& surface_format : surface_formats)
{ {
// Some drivers seem to return a SRGB format here (Intel Mesa). // Some drivers seem to return a RGBA8 SRGB format here (Intel Mesa).
// This results in gamma correction when presenting to the screen, which we don't want. // Some other drivers return both a RGBA8 SRGB and UNORM formats (Nvidia).
// Use a linear format instead, if this is the case. // This results in gamma correction when presenting to the screen, which we don't want,
// because we already apply gamma ourselves, and we might not use sRGB gamma.
// Force using a linear format instead, if this is the case.
VkFormat format = VKTexture::GetLinearFormat(surface_format.format); VkFormat format = VKTexture::GetLinearFormat(surface_format.format);
if (format == VK_FORMAT_R8G8B8A8_UNORM)
surface_format_RGBA8 = &surface_format;
else if (format == VK_FORMAT_B8G8R8A8_UNORM)
surface_format_BGRA8 = &surface_format;
else if (format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 &&
surface_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
surface_format_RGB10_A2 = &surface_format;
else if (format == VK_FORMAT_R16G16B16A16_SFLOAT &&
surface_format.colorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT)
surface_format_RGBA16F_scRGB = &surface_format;
else
continue;
}
const VkSurfaceFormatKHR* surface_format = nullptr;
// Pick the best format.
// "g_ActiveConfig" might not have been been updated yet.
if (g_Config.bHDR && surface_format_RGBA16F_scRGB)
surface_format = surface_format_RGBA16F_scRGB;
else if (surface_format_RGB10_A2)
surface_format = surface_format_RGB10_A2;
else if (surface_format_RGBA8)
surface_format = surface_format_RGBA8;
else if (surface_format_BGRA8)
surface_format = surface_format_BGRA8;
if (surface_format)
{
const VkFormat format = VKTexture::GetLinearFormat(surface_format->format);
if (format == VK_FORMAT_R8G8B8A8_UNORM) if (format == VK_FORMAT_R8G8B8A8_UNORM)
m_texture_format = AbstractTextureFormat::RGBA8; m_texture_format = AbstractTextureFormat::RGBA8;
else if (format == VK_FORMAT_B8G8R8A8_UNORM) else if (format == VK_FORMAT_B8G8R8A8_UNORM)
m_texture_format = AbstractTextureFormat::BGRA8; m_texture_format = AbstractTextureFormat::BGRA8;
else else if (format == VK_FORMAT_A2B10G10R10_UNORM_PACK32)
continue; m_texture_format = AbstractTextureFormat::RGB10_A2;
else if (format == VK_FORMAT_R16G16B16A16_SFLOAT)
m_texture_format = AbstractTextureFormat::RGBA16F;
m_surface_format.format = format; m_surface_format.format = format;
m_surface_format.colorSpace = surface_format.colorSpace; m_surface_format.colorSpace = surface_format->colorSpace;
return true; return true;
} }

View File

@ -190,7 +190,7 @@ VkFormat VKTexture::GetVkFormatForHostTextureFormat(AbstractTextureFormat format
return VK_FORMAT_B8G8R8A8_UNORM; return VK_FORMAT_B8G8R8A8_UNORM;
case AbstractTextureFormat::RGB10_A2: case AbstractTextureFormat::RGB10_A2:
return VK_FORMAT_A2R10G10B10_UNORM_PACK32; return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case AbstractTextureFormat::RGBA16F: case AbstractTextureFormat::RGBA16F:
return VK_FORMAT_R16G16B16A16_SFLOAT; return VK_FORMAT_R16G16B16A16_SFLOAT;

View File

@ -382,6 +382,7 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config)
config->backend_info.bSupportsPartialMultisampleResolve = true; // Assumed support. config->backend_info.bSupportsPartialMultisampleResolve = true; // Assumed support.
config->backend_info.bSupportsDynamicVertexLoader = true; // Assumed support. config->backend_info.bSupportsDynamicVertexLoader = true; // Assumed support.
config->backend_info.bSupportsVSLinePointExpand = true; // Assumed support. config->backend_info.bSupportsVSLinePointExpand = true; // Assumed support.
config->backend_info.bSupportsHDROutput = true; // Assumed support.
} }
void VulkanContext::PopulateBackendInfoAdapters(VideoConfig* config, const GPUList& gpu_list) void VulkanContext::PopulateBackendInfoAdapters(VideoConfig* config, const GPUList& gpu_list)

View File

@ -177,5 +177,5 @@ bool AbstractGfx::UseGeometryShaderForUI() const
// OpenGL doesn't render to a 2-layer backbuffer like D3D/Vulkan for quad-buffered stereo, // OpenGL doesn't render to a 2-layer backbuffer like D3D/Vulkan for quad-buffered stereo,
// instead drawing twice and the eye selected by glDrawBuffer() (see Presenter::RenderXFBToScreen) // instead drawing twice and the eye selected by glDrawBuffer() (see Presenter::RenderXFBToScreen)
return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer && return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer &&
g_ActiveConfig.backend_info.api_type != APIType::OpenGL; !g_ActiveConfig.backend_info.bUsesExplictQuadBuffering;
} }

View File

@ -159,8 +159,8 @@ public:
// Called when the configuration changes, and backend structures need to be updated. // Called when the configuration changes, and backend structures need to be updated.
virtual void OnConfigChanged(u32 changed_bits); virtual void OnConfigChanged(u32 changed_bits);
// Returns true if a layer-expanding geometry shader should be used when rendering the user // Returns true if a layer-expanding geometry shader should be used when rendering
// interface and final XFB. // the user interface on the output buffer.
bool UseGeometryShaderForUI() const; bool UseGeometryShaderForUI() const;
// Returns info about the main surface (aka backbuffer) // Returns info about the main surface (aka backbuffer)

View File

@ -672,7 +672,7 @@ std::string GenerateImGuiVertexShader()
return code.GetBuffer(); return code.GetBuffer();
} }
std::string GenerateImGuiPixelShader() std::string GenerateImGuiPixelShader(bool linear_space_output)
{ {
ShaderCode code; ShaderCode code;
EmitSamplerDeclarations(code, 0, 1, false); EmitSamplerDeclarations(code, 0, 1, false);
@ -680,8 +680,13 @@ std::string GenerateImGuiPixelShader()
code.Write("{{\n" code.Write("{{\n"
" ocol0 = "); " ocol0 = ");
EmitSampleTexture(code, 0, "float3(v_tex0.xy, 0.0)"); EmitSampleTexture(code, 0, "float3(v_tex0.xy, 0.0)");
code.Write(" * v_col0;\n" // We approximate to gamma 2.2 instead of sRGB as it barely matters for this case.
"}}\n"); // Note that if HDR is enabled, ideally we should multiply by
// the paper white brightness for readability.
if (linear_space_output)
code.Write(" * pow(v_col0, float4(2.2f, 2.2f, 2.2f, 1.0f));\n}}\n");
else
code.Write(" * v_col0;\n}}\n");
return code.GetBuffer(); return code.GetBuffer();
} }

View File

@ -24,6 +24,6 @@ std::string GenerateFormatConversionShader(EFBReinterpretType convtype, u32 samp
std::string GenerateTextureReinterpretShader(TextureFormat from_format, TextureFormat to_format); std::string GenerateTextureReinterpretShader(TextureFormat from_format, TextureFormat to_format);
std::string GenerateEFBRestorePixelShader(); std::string GenerateEFBRestorePixelShader();
std::string GenerateImGuiVertexShader(); std::string GenerateImGuiVertexShader();
std::string GenerateImGuiPixelShader(); std::string GenerateImGuiPixelShader(bool linear_space_output = false);
} // namespace FramebufferShaderGen } // namespace FramebufferShaderGen

View File

@ -120,11 +120,15 @@ bool OnScreenUI::RecompileImGuiPipeline()
return true; return true;
} }
const bool linear_space_output =
g_presenter->GetBackbufferFormat() == AbstractTextureFormat::RGBA16F;
std::unique_ptr<AbstractShader> vertex_shader = g_gfx->CreateShaderFromSource( std::unique_ptr<AbstractShader> vertex_shader = g_gfx->CreateShaderFromSource(
ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(), ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(),
"ImGui vertex shader"); "ImGui vertex shader");
std::unique_ptr<AbstractShader> pixel_shader = g_gfx->CreateShaderFromSource( std::unique_ptr<AbstractShader> pixel_shader = g_gfx->CreateShaderFromSource(
ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(), "ImGui pixel shader"); ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(linear_space_output),
"ImGui pixel shader");
if (!vertex_shader || !pixel_shader) if (!vertex_shader || !pixel_shader)
{ {
PanicAlertFmt("Failed to compile ImGui shaders"); PanicAlertFmt("Failed to compile ImGui shaders");

View File

@ -33,7 +33,32 @@
namespace VideoCommon namespace VideoCommon
{ {
static const char s_default_shader[] = "void main() { SetOutput(Sample()); }\n"; static const char s_empty_pixel_shader[] = "void main() { SetOutput(Sample()); }\n";
static const char s_default_pixel_shader_name[] = "default_pre_post_process";
// Keep the highest quality possible to avoid losing quality on subtle gamma conversions.
// RGBA16F should have enough quality even if we store colors in gamma space on it.
static const AbstractTextureFormat s_intermediary_buffer_format = AbstractTextureFormat::RGBA16F;
bool LoadShaderFromFile(const std::string& shader, const std::string& sub_dir,
std::string& out_code)
{
std::string path = File::GetUserPath(D_SHADERS_IDX) + sub_dir + shader + ".glsl";
if (!File::Exists(path))
{
// Fallback to shared user dir
path = File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir + shader + ".glsl";
}
if (!File::ReadFileToString(path, out_code))
{
out_code = "";
ERROR_LOG_FMT(VIDEO, "Post-processing shader not found: {}", path);
return false;
}
return true;
}
PostProcessingConfiguration::PostProcessingConfiguration() = default; PostProcessingConfiguration::PostProcessingConfiguration() = default;
@ -60,24 +85,16 @@ void PostProcessingConfiguration::LoadShader(const std::string& shader)
sub_dir = PASSIVE_DIR DIR_SEP; sub_dir = PASSIVE_DIR DIR_SEP;
} }
// loading shader code
std::string code; std::string code;
std::string path = File::GetUserPath(D_SHADERS_IDX) + sub_dir + shader + ".glsl"; if (!LoadShaderFromFile(shader, sub_dir, code))
if (!File::Exists(path))
{ {
// Fallback to shared user dir
path = File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir + shader + ".glsl";
}
if (!File::ReadFileToString(path, code))
{
ERROR_LOG_FMT(VIDEO, "Post-processing shader not found: {}", path);
LoadDefaultShader(); LoadDefaultShader();
return; return;
} }
LoadOptions(code); LoadOptions(code);
// Note that this will build the shaders with the custom options values users
// might have set in the settings
LoadOptionsConfiguration(); LoadOptionsConfiguration();
m_current_shader_code = code; m_current_shader_code = code;
} }
@ -86,7 +103,8 @@ void PostProcessingConfiguration::LoadDefaultShader()
{ {
m_options.clear(); m_options.clear();
m_any_options_dirty = false; m_any_options_dirty = false;
m_current_shader_code = s_default_shader; m_current_shader = "";
m_current_shader_code = s_empty_pixel_shader;
} }
void PostProcessingConfiguration::LoadOptions(const std::string& code) void PostProcessingConfiguration::LoadOptions(const std::string& code)
@ -242,6 +260,7 @@ void PostProcessingConfiguration::LoadOptionsConfiguration()
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
std::string section = m_current_shader + "-options"; std::string section = m_current_shader + "-options";
// We already expect all the options to be marked as "dirty" when we reach here
for (auto& it : m_options) for (auto& it : m_options)
{ {
switch (it.second.m_type) switch (it.second.m_type)
@ -375,6 +394,8 @@ static std::vector<std::string> GetShaders(const std::string& sub_dir = "")
{ {
std::string name; std::string name;
SplitPath(path, nullptr, &name, nullptr); SplitPath(path, nullptr, &name, nullptr);
if (name == s_default_pixel_shader_name)
continue;
result.push_back(name); result.push_back(name);
} }
return result; return result;
@ -409,8 +430,15 @@ bool PostProcessing::Initialize(AbstractTextureFormat format)
void PostProcessing::RecompileShader() void PostProcessing::RecompileShader()
{ {
// Note: for simplicity we already recompile all the shaders
// and pipelines even if there might not be need to.
m_default_pipeline.reset();
m_pipeline.reset(); m_pipeline.reset();
m_default_pixel_shader.reset();
m_pixel_shader.reset(); m_pixel_shader.reset();
m_default_vertex_shader.reset();
m_vertex_shader.reset();
if (!CompilePixelShader()) if (!CompilePixelShader())
return; return;
if (!CompileVertexShader()) if (!CompileVertexShader())
@ -421,10 +449,27 @@ void PostProcessing::RecompileShader()
void PostProcessing::RecompilePipeline() void PostProcessing::RecompilePipeline()
{ {
m_default_pipeline.reset();
m_pipeline.reset(); m_pipeline.reset();
CompilePipeline(); CompilePipeline();
} }
bool PostProcessing::IsColorCorrectionActive() const
{
// We can skip the color correction pass if none of these settings are on
// (it might have still helped with gamma correct sampling, but it's not worth running it).
return g_ActiveConfig.color_correction.bCorrectColorSpace ||
g_ActiveConfig.color_correction.bCorrectGamma ||
m_framebuffer_format == AbstractTextureFormat::RGBA16F;
}
bool PostProcessing::NeedsIntermediaryBuffer() const
{
// If we have no user selected post process shader,
// there's no point in having an intermediary buffer doing nothing.
return !m_config.GetShader().empty();
}
void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst, void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
const MathUtil::Rectangle<int>& src, const MathUtil::Rectangle<int>& src,
const AbstractTexture* src_tex, int src_layer) const AbstractTexture* src_tex, int src_layer)
@ -435,37 +480,150 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
RecompilePipeline(); RecompilePipeline();
} }
if (!m_pipeline) // By default all source layers will be copied into the respective target layers
return; const bool copy_all_layers = src_layer < 0;
src_layer = std::max(src_layer, 0);
FillUniformBuffer(src, src_tex, src_layer); MathUtil::Rectangle<int> src_rect = src;
g_vertex_manager->UploadUtilityUniforms(m_uniform_staging_buffer.data(), g_gfx->SetSamplerState(0, RenderState::GetLinearSamplerState());
static_cast<u32>(m_uniform_staging_buffer.size())); g_gfx->SetTexture(0, src_tex);
const bool is_color_correction_active = IsColorCorrectionActive();
const bool needs_intermediary_buffer = NeedsIntermediaryBuffer();
const AbstractPipeline* final_pipeline = m_pipeline.get();
std::vector<u8>* uniform_staging_buffer = &m_default_uniform_staging_buffer;
bool default_uniform_staging_buffer = true;
// Intermediary pass.
// We draw to a high quality intermediary texture for two reasons:
// -Keep quality for gamma and gamut conversions, and HDR output
// (low bit depths lose too much quality with gamma conversions)
// -We make a texture of the exact same res as the source one,
// because all the post process shaders we already had assume that
// the source texture size (EFB) is different from the swap chain
// texture size (which matches the window size).
if (m_default_pipeline && is_color_correction_active && needs_intermediary_buffer)
{
AbstractFramebuffer* const previous_framebuffer = g_gfx->GetCurrentFramebuffer();
// We keep the min number of layers as the render target,
// as in case of OpenGL, the source FBX will have two layers,
// but we will render onto two separate frame buffers (one by one),
// so it would be a waste to allocate two layers (see "bUsesExplictQuadBuffering").
const u32 target_layers = copy_all_layers ? src_tex->GetLayers() : 1;
if (!m_intermediary_frame_buffer || !m_intermediary_color_texture ||
m_intermediary_color_texture.get()->GetWidth() != static_cast<u32>(src_rect.GetWidth()) ||
m_intermediary_color_texture.get()->GetHeight() != static_cast<u32>(src_rect.GetHeight()) ||
m_intermediary_color_texture.get()->GetLayers() != target_layers)
{
const TextureConfig intermediary_color_texture_config(
src_rect.GetWidth(), src_rect.GetHeight(), 1, target_layers, src_tex->GetSamples(),
s_intermediary_buffer_format, AbstractTextureFlag_RenderTarget);
m_intermediary_color_texture = g_gfx->CreateTexture(intermediary_color_texture_config,
"Intermediary post process texture");
m_intermediary_frame_buffer =
g_gfx->CreateFramebuffer(m_intermediary_color_texture.get(), nullptr);
}
g_gfx->SetFramebuffer(m_intermediary_frame_buffer.get());
FillUniformBuffer(src_rect, src_tex, src_layer, g_gfx->GetCurrentFramebuffer()->GetRect(),
g_presenter->GetTargetRectangle(), uniform_staging_buffer->data(),
!default_uniform_staging_buffer);
g_vertex_manager->UploadUtilityUniforms(uniform_staging_buffer->data(),
static_cast<u32>(uniform_staging_buffer->size()));
g_gfx->SetViewportAndScissor(g_gfx->ConvertFramebufferRectangle(
m_intermediary_color_texture->GetRect(), m_intermediary_frame_buffer.get()));
g_gfx->SetPipeline(m_default_pipeline.get());
g_gfx->Draw(0, 3);
g_gfx->SetFramebuffer(previous_framebuffer);
src_rect = m_intermediary_color_texture->GetRect();
src_tex = m_intermediary_color_texture.get();
g_gfx->SetTexture(0, src_tex);
// The "m_intermediary_color_texture" has already copied
// from the specified source layer onto its first one.
// If we query for a layer that the source texture doesn't have,
// it will fall back on the first one anyway.
src_layer = 0;
uniform_staging_buffer = &m_uniform_staging_buffer;
default_uniform_staging_buffer = false;
}
else
{
// If we have no custom user shader selected, and color correction
// is active, directly run the fixed pipeline shader instead of
// doing two passes, with the second one doing nothing useful.
if (m_default_pipeline && is_color_correction_active)
{
final_pipeline = m_default_pipeline.get();
}
else
{
uniform_staging_buffer = &m_uniform_staging_buffer;
default_uniform_staging_buffer = false;
}
m_intermediary_frame_buffer.release();
m_intermediary_color_texture.release();
}
// TODO: ideally we'd do the user selected post process pass in the intermediary buffer in linear
// space (instead of gamma space), so the shaders could act more accurately (and sample in linear
// space), though that would break the look of some of current post processes we have, and thus is
// better avoided for now.
// Final pass, either a user selected shader or the default (fixed) shader.
if (final_pipeline)
{
FillUniformBuffer(src_rect, src_tex, src_layer, g_gfx->GetCurrentFramebuffer()->GetRect(),
g_presenter->GetTargetRectangle(), uniform_staging_buffer->data(),
!default_uniform_staging_buffer);
g_vertex_manager->UploadUtilityUniforms(uniform_staging_buffer->data(),
static_cast<u32>(uniform_staging_buffer->size()));
g_gfx->SetViewportAndScissor( g_gfx->SetViewportAndScissor(
g_gfx->ConvertFramebufferRectangle(dst, g_gfx->GetCurrentFramebuffer())); g_gfx->ConvertFramebufferRectangle(dst, g_gfx->GetCurrentFramebuffer()));
g_gfx->SetPipeline(m_pipeline.get()); g_gfx->SetPipeline(final_pipeline);
g_gfx->SetTexture(0, src_tex);
g_gfx->SetSamplerState(0, RenderState::GetLinearSamplerState());
g_gfx->Draw(0, 3); g_gfx->Draw(0, 3);
}
} }
std::string PostProcessing::GetUniformBufferHeader() const std::string PostProcessing::GetUniformBufferHeader(bool user_post_process) const
{ {
std::ostringstream ss; std::ostringstream ss;
u32 unused_counter = 1; u32 unused_counter = 1;
ss << "UBO_BINDING(std140, 1) uniform PSBlock {\n"; ss << "UBO_BINDING(std140, 1) uniform PSBlock {\n";
// Builtin uniforms // Builtin uniforms:
ss << " float4 resolution;\n";
ss << " float4 resolution;\n"; // Source resolution
ss << " float4 target_resolution;\n";
ss << " float4 window_resolution;\n"; ss << " float4 window_resolution;\n";
// How many horizontal and vertical stereo views do we have? (set to 1 when we use layers instead)
ss << " int2 stereo_views;\n";
ss << " float4 src_rect;\n"; ss << " float4 src_rect;\n";
// The first (but not necessarily only) source layer we target
ss << " int src_layer;\n"; ss << " int src_layer;\n";
ss << " uint time;\n"; ss << " uint time;\n";
for (u32 i = 0; i < 2; i++)
ss << " uint ubo_align_" << unused_counter++ << "_;\n";
ss << "\n";
ss << " int correct_color_space;\n";
ss << " int game_color_space;\n";
ss << " int correct_gamma;\n";
ss << " float game_gamma;\n";
ss << " int sdr_display_gamma_sRGB;\n";
ss << " float sdr_display_custom_gamma;\n";
ss << " int linear_space_output;\n";
ss << " int hdr_output;\n";
ss << " float hdr_paper_white_nits;\n";
ss << " float hdr_sdr_white_nits;\n";
if (user_post_process)
{
ss << "\n";
// Custom options/uniforms // Custom options/uniforms
for (const auto& it : m_config.GetOptions()) for (const auto& it : m_config.GetOptions())
{ {
@ -500,16 +658,18 @@ std::string PostProcessing::GetUniformBufferHeader() const
ss << " float ubo_align_" << unused_counter++ << "_;\n"; ss << " float ubo_align_" << unused_counter++ << "_;\n";
} }
} }
}
ss << "};\n\n"; ss << "};\n\n";
return ss.str(); return ss.str();
} }
std::string PostProcessing::GetHeader() const std::string PostProcessing::GetHeader(bool user_post_process) const
{ {
std::ostringstream ss; std::ostringstream ss;
ss << GetUniformBufferHeader(); ss << GetUniformBufferHeader(user_post_process);
ss << "SAMPLER_BINDING(0) uniform sampler2DArray samp0;\n"; ss << "SAMPLER_BINDING(0) uniform sampler2DArray samp0;\n";
ss << "SAMPLER_BINDING(1) uniform sampler2DArray samp1;\n";
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders) if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
{ {
@ -530,6 +690,16 @@ float4 SampleLocation(float2 location) { return texture(samp0, float3(location,
float4 SampleLayer(int layer) { return texture(samp0, float3(v_tex0.xy, float(layer))); } float4 SampleLayer(int layer) { return texture(samp0, float3(v_tex0.xy, float(layer))); }
#define SampleOffset(offset) textureOffset(samp0, v_tex0, offset) #define SampleOffset(offset) textureOffset(samp0, v_tex0, offset)
float2 GetTargetResolution()
{
return target_resolution.xy;
}
float2 GetInvTargetResolution()
{
return target_resolution.zw;
}
float2 GetWindowResolution() float2 GetWindowResolution()
{ {
return window_resolution.xy; return window_resolution.xy;
@ -585,7 +755,9 @@ std::string PostProcessing::GetFooter() const
bool PostProcessing::CompileVertexShader() bool PostProcessing::CompileVertexShader()
{ {
std::ostringstream ss; std::ostringstream ss;
ss << GetUniformBufferHeader(); // We never need the user selected post process custom uniforms in the vertex shader
const bool user_post_process = false;
ss << GetUniformBufferHeader(user_post_process);
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders) if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
{ {
@ -605,16 +777,28 @@ bool PostProcessing::CompileVertexShader()
ss << " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n"; ss << " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n";
ss << " v_tex0 = float3(src_rect.xy + (src_rect.zw * v_tex0.xy), float(src_layer));\n"; ss << " v_tex0 = float3(src_rect.xy + (src_rect.zw * v_tex0.xy), float(src_layer));\n";
// Vulkan Y needs to be inverted on every pass
if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan) if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan)
ss << " opos.y = -opos.y;\n"; ss << " opos.y = -opos.y;\n";
std::string s2 = ss.str();
s2 += "}\n";
m_default_vertex_shader = g_gfx->CreateShaderFromSource(ShaderStage::Vertex, s2,
"Default post-processing vertex shader");
// OpenGL Y needs to be inverted once only (in the last pass)
if (g_ActiveConfig.backend_info.api_type == APIType::OpenGL)
ss << " opos.y = -opos.y;\n";
ss << "}\n"; ss << "}\n";
m_vertex_shader = m_vertex_shader =
g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss.str(), "Post-processing vertex shader"); g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss.str(), "Post-processing vertex shader");
if (!m_vertex_shader) if (!m_default_vertex_shader || !m_vertex_shader)
{ {
PanicAlertFmt("Failed to compile post-processing vertex shader"); PanicAlertFmt("Failed to compile post-processing vertex shader");
m_default_vertex_shader.reset();
m_vertex_shader.reset();
return false; return false;
} }
@ -623,44 +807,86 @@ bool PostProcessing::CompileVertexShader()
struct BuiltinUniforms struct BuiltinUniforms
{ {
float resolution[4]; // bools need to be represented as "s32"
float window_resolution[4];
float src_rect[4]; std::array<float, 4> source_resolution;
std::array<float, 4> target_resolution;
std::array<float, 4> window_resolution;
std::array<float, 4> stereo_views;
std::array<float, 4> src_rect;
s32 src_layer; s32 src_layer;
u32 time; u32 time;
u32 padding[2]; s32 correct_color_space;
s32 game_color_space;
s32 correct_gamma;
float game_gamma;
s32 sdr_display_gamma_sRGB;
float sdr_display_custom_gamma;
s32 linear_space_output;
s32 hdr_output;
float hdr_paper_white_nits;
float hdr_sdr_white_nits;
}; };
size_t PostProcessing::CalculateUniformsSize() const size_t PostProcessing::CalculateUniformsSize(bool user_post_process) const
{ {
// Allocate a vec4 for each uniform to simplify allocation. // Allocate a vec4 for each uniform to simplify allocation.
return sizeof(BuiltinUniforms) + m_config.GetOptions().size() * sizeof(float) * 4; return sizeof(BuiltinUniforms) +
(user_post_process ? m_config.GetOptions().size() : 0) * sizeof(float) * 4;
} }
void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src, void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
const AbstractTexture* src_tex, int src_layer) const AbstractTexture* src_tex, int src_layer,
const MathUtil::Rectangle<int>& dst,
const MathUtil::Rectangle<int>& wnd, u8* buffer,
bool user_post_process)
{ {
const auto& window_rect = g_presenter->GetTargetRectangle();
const float rcp_src_width = 1.0f / src_tex->GetWidth(); const float rcp_src_width = 1.0f / src_tex->GetWidth();
const float rcp_src_height = 1.0f / src_tex->GetHeight(); const float rcp_src_height = 1.0f / src_tex->GetHeight();
BuiltinUniforms builtin_uniforms = {
{static_cast<float>(src_tex->GetWidth()), static_cast<float>(src_tex->GetHeight()), BuiltinUniforms builtin_uniforms;
rcp_src_width, rcp_src_height}, builtin_uniforms.source_resolution = {static_cast<float>(src_tex->GetWidth()),
{static_cast<float>(window_rect.GetWidth()), static_cast<float>(window_rect.GetHeight()), static_cast<float>(src_tex->GetHeight()), rcp_src_width,
1.0f / static_cast<float>(window_rect.GetWidth()), rcp_src_height};
1.0f / static_cast<float>(window_rect.GetHeight())}, builtin_uniforms.target_resolution = {
{static_cast<float>(src.left) * rcp_src_width, static_cast<float>(src.top) * rcp_src_height, static_cast<float>(dst.GetWidth()), static_cast<float>(dst.GetHeight()),
1.0f / static_cast<float>(dst.GetWidth()), 1.0f / static_cast<float>(dst.GetHeight())};
builtin_uniforms.window_resolution = {
static_cast<float>(wnd.GetWidth()), static_cast<float>(wnd.GetHeight()),
1.0f / static_cast<float>(wnd.GetWidth()), 1.0f / static_cast<float>(wnd.GetHeight())};
builtin_uniforms.src_rect = {static_cast<float>(src.left) * rcp_src_width,
static_cast<float>(src.top) * rcp_src_height,
static_cast<float>(src.GetWidth()) * rcp_src_width, static_cast<float>(src.GetWidth()) * rcp_src_width,
static_cast<float>(src.GetHeight()) * rcp_src_height}, static_cast<float>(src.GetHeight()) * rcp_src_height};
static_cast<s32>(src_layer), builtin_uniforms.src_layer = static_cast<s32>(src_layer);
static_cast<u32>(m_timer.ElapsedMs()), builtin_uniforms.time = static_cast<u32>(m_timer.ElapsedMs());
};
u8* buf = m_uniform_staging_buffer.data(); // Color correction related uniforms.
std::memcpy(buf, &builtin_uniforms, sizeof(builtin_uniforms)); // These are mainly used by the "m_default_pixel_shader",
buf += sizeof(builtin_uniforms); // but should also be accessible to all other shaders.
builtin_uniforms.correct_color_space = g_ActiveConfig.color_correction.bCorrectColorSpace;
builtin_uniforms.game_color_space =
static_cast<int>(g_ActiveConfig.color_correction.game_color_space);
builtin_uniforms.correct_gamma = g_ActiveConfig.color_correction.bCorrectGamma;
builtin_uniforms.game_gamma = g_ActiveConfig.color_correction.fGameGamma;
builtin_uniforms.sdr_display_gamma_sRGB = g_ActiveConfig.color_correction.bSDRDisplayGammaSRGB;
builtin_uniforms.sdr_display_custom_gamma =
g_ActiveConfig.color_correction.fSDRDisplayCustomGamma;
// scRGB (RGBA16F) expects linear values as opposed to sRGB gamma
builtin_uniforms.linear_space_output = m_framebuffer_format == AbstractTextureFormat::RGBA16F;
// Implies ouput values can be beyond the 0-1 range
builtin_uniforms.hdr_output = m_framebuffer_format == AbstractTextureFormat::RGBA16F;
builtin_uniforms.hdr_paper_white_nits = g_ActiveConfig.color_correction.fHDRPaperWhiteNits;
// A value of 1 1 1 usually matches 80 nits in HDR
builtin_uniforms.hdr_sdr_white_nits = 80.f;
for (const auto& it : m_config.GetOptions()) std::memcpy(buffer, &builtin_uniforms, sizeof(builtin_uniforms));
buffer += sizeof(builtin_uniforms);
if (!user_post_process)
return;
for (auto& it : m_config.GetOptions())
{ {
union union
{ {
@ -688,53 +914,116 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
break; break;
} }
std::memcpy(buf, &value, sizeof(value)); it.second.m_dirty = false;
buf += sizeof(value);
std::memcpy(buffer, &value, sizeof(value));
buffer += sizeof(value);
} }
m_config.SetDirty(false);
} }
bool PostProcessing::CompilePixelShader() bool PostProcessing::CompilePixelShader()
{ {
m_pipeline.reset(); m_default_pixel_shader.reset();
m_pixel_shader.reset(); m_pixel_shader.reset();
// Generate GLSL and compile the new shader. // Generate GLSL and compile the new shaders:
std::string default_pixel_shader_code;
if (LoadShaderFromFile(s_default_pixel_shader_name, "", default_pixel_shader_code))
{
m_default_pixel_shader = g_gfx->CreateShaderFromSource(
ShaderStage::Pixel, GetHeader(false) + default_pixel_shader_code + GetFooter(),
"Default post-processing pixel shader");
// We continue even if all of this failed, it doesn't matter
m_default_uniform_staging_buffer.resize(CalculateUniformsSize(false));
}
else
{
m_default_uniform_staging_buffer.resize(0);
}
m_config.LoadShader(g_ActiveConfig.sPostProcessingShader); m_config.LoadShader(g_ActiveConfig.sPostProcessingShader);
m_pixel_shader = g_gfx->CreateShaderFromSource( m_pixel_shader = g_gfx->CreateShaderFromSource(
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(), ShaderStage::Pixel, GetHeader(true) + m_config.GetShaderCode() + GetFooter(),
fmt::format("Post-processing pixel shader: {}", m_config.GetShader())); fmt::format("User post-processing pixel shader: {}", m_config.GetShader()));
if (!m_pixel_shader) if (!m_pixel_shader)
{ {
PanicAlertFmt("Failed to compile post-processing shader {}", m_config.GetShader()); PanicAlertFmt("Failed to compile user post-processing shader {}", m_config.GetShader());
// Use default shader. // Use default shader.
m_config.LoadDefaultShader(); m_config.LoadDefaultShader();
m_pixel_shader = g_gfx->CreateShaderFromSource( m_pixel_shader = g_gfx->CreateShaderFromSource(
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(), ShaderStage::Pixel, GetHeader(true) + m_config.GetShaderCode() + GetFooter(),
"Default post-processing pixel shader"); "Default user post-processing pixel shader");
if (!m_pixel_shader) if (!m_pixel_shader)
{
m_uniform_staging_buffer.resize(0);
return false; return false;
} }
}
m_uniform_staging_buffer.resize(CalculateUniformsSize()); m_uniform_staging_buffer.resize(CalculateUniformsSize(true));
return true; return true;
} }
bool UseGeometryShaderForPostProcess(bool is_intermediary_buffer)
{
// We only return true on stereo modes that need to copy
// both source texture layers into the target texture layers.
// Any other case is handled manually with multiple copies, thus
// it doesn't need a geom shader.
switch (g_ActiveConfig.stereo_mode)
{
case StereoMode::QuadBuffer:
return !g_ActiveConfig.backend_info.bUsesExplictQuadBuffering;
case StereoMode::Anaglyph:
case StereoMode::Passive:
return is_intermediary_buffer;
case StereoMode::SBS:
case StereoMode::TAB:
case StereoMode::Off:
default:
return false;
}
}
bool PostProcessing::CompilePipeline() bool PostProcessing::CompilePipeline()
{ {
// Not needed. Some backends don't like making pipelines with no targets,
// and in any case, we don't need to render anything if that happened.
if (m_framebuffer_format == AbstractTextureFormat::Undefined) if (m_framebuffer_format == AbstractTextureFormat::Undefined)
return true; // Not needed (some backends don't like making pipelines with no targets) return true;
// If this is true, the "m_default_pipeline" won't be the only one that runs
const bool needs_intermediary_buffer = NeedsIntermediaryBuffer();
AbstractPipelineConfig config = {}; AbstractPipelineConfig config = {};
config.vertex_shader = m_vertex_shader.get(); config.vertex_shader =
config.geometry_shader = needs_intermediary_buffer ? m_vertex_shader.get() : m_default_vertex_shader.get();
g_gfx->UseGeometryShaderForUI() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr; // This geometry shader will take care of reading both layer 0 and 1 on the source texture,
config.pixel_shader = m_pixel_shader.get(); // and writing to both layer 0 and 1 on the render target.
config.geometry_shader = UseGeometryShaderForPostProcess(needs_intermediary_buffer) ?
g_shader_cache->GetTexcoordGeometryShader() :
nullptr;
config.pixel_shader = m_default_pixel_shader.get();
config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
config.depth_state = RenderState::GetNoDepthTestingDepthState(); config.depth_state = RenderState::GetNoDepthTestingDepthState();
config.blending_state = RenderState::GetNoBlendingBlendState(); config.blending_state = RenderState::GetNoBlendingBlendState();
config.framebuffer_state = RenderState::GetColorFramebufferState(m_framebuffer_format); config.framebuffer_state = RenderState::GetColorFramebufferState(
needs_intermediary_buffer ? s_intermediary_buffer_format : m_framebuffer_format);
config.usage = AbstractPipelineUsage::Utility; config.usage = AbstractPipelineUsage::Utility;
// We continue even if it failed, it will be skipped later on
if (config.pixel_shader)
m_default_pipeline = g_gfx->CreatePipeline(config);
config.vertex_shader = m_default_vertex_shader.get();
config.geometry_shader = UseGeometryShaderForPostProcess(false) ?
g_shader_cache->GetTexcoordGeometryShader() :
nullptr;
config.pixel_shader = m_pixel_shader.get();
config.framebuffer_state = RenderState::GetColorFramebufferState(m_framebuffer_format);
m_pipeline = g_gfx->CreatePipeline(config); m_pipeline = g_gfx->CreatePipeline(config);
if (!m_pipeline) if (!m_pipeline)
return false; return false;

View File

@ -16,12 +16,14 @@
class AbstractPipeline; class AbstractPipeline;
class AbstractShader; class AbstractShader;
class AbstractTexture; class AbstractTexture;
class AbstractFramebuffer;
namespace VideoCommon namespace VideoCommon
{ {
class PostProcessingConfiguration class PostProcessingConfiguration
{ {
public: public:
// User defined post process
struct ConfigurationOption struct ConfigurationOption
{ {
enum class OptionType enum class OptionType
@ -105,29 +107,43 @@ public:
void RecompilePipeline(); void RecompilePipeline();
void BlitFromTexture(const MathUtil::Rectangle<int>& dst, const MathUtil::Rectangle<int>& src, void BlitFromTexture(const MathUtil::Rectangle<int>& dst, const MathUtil::Rectangle<int>& src,
const AbstractTexture* src_tex, int src_layer); const AbstractTexture* src_tex, int src_layer = -1);
bool IsColorCorrectionActive() const;
bool NeedsIntermediaryBuffer() const;
protected: protected:
std::string GetUniformBufferHeader() const; std::string GetUniformBufferHeader(bool user_post_process) const;
std::string GetHeader() const; std::string GetHeader(bool user_post_process) const;
std::string GetFooter() const; std::string GetFooter() const;
bool CompileVertexShader(); bool CompileVertexShader();
bool CompilePixelShader(); bool CompilePixelShader();
bool CompilePipeline(); bool CompilePipeline();
size_t CalculateUniformsSize() const; size_t CalculateUniformsSize(bool user_post_process) const;
void FillUniformBuffer(const MathUtil::Rectangle<int>& src, const AbstractTexture* src_tex, void FillUniformBuffer(const MathUtil::Rectangle<int>& src, const AbstractTexture* src_tex,
int src_layer); int src_layer, const MathUtil::Rectangle<int>& dst,
const MathUtil::Rectangle<int>& wnd, u8* buffer, bool user_post_process);
// Timer for determining our time value // Timer for determining our time value
Common::Timer m_timer; Common::Timer m_timer;
PostProcessingConfiguration m_config;
// Dolphin fixed post process:
PostProcessingConfiguration::ConfigMap m_default_options;
std::unique_ptr<AbstractShader> m_default_vertex_shader;
std::unique_ptr<AbstractShader> m_default_pixel_shader;
std::unique_ptr<AbstractPipeline> m_default_pipeline;
std::unique_ptr<AbstractFramebuffer> m_intermediary_frame_buffer;
std::unique_ptr<AbstractTexture> m_intermediary_color_texture;
std::vector<u8> m_default_uniform_staging_buffer;
// User post process:
PostProcessingConfiguration m_config;
std::unique_ptr<AbstractShader> m_vertex_shader; std::unique_ptr<AbstractShader> m_vertex_shader;
std::unique_ptr<AbstractShader> m_pixel_shader; std::unique_ptr<AbstractShader> m_pixel_shader;
std::unique_ptr<AbstractPipeline> m_pipeline; std::unique_ptr<AbstractPipeline> m_pipeline;
AbstractTextureFormat m_framebuffer_format = AbstractTextureFormat::Undefined;
std::vector<u8> m_uniform_staging_buffer; std::vector<u8> m_uniform_staging_buffer;
AbstractTextureFormat m_framebuffer_format = AbstractTextureFormat::Undefined;
}; };
} // namespace VideoCommon } // namespace VideoCommon

View File

@ -514,9 +514,11 @@ void Presenter::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
m_post_processor->BlitFromTexture(left_rc, source_rc, source_texture, 0); m_post_processor->BlitFromTexture(left_rc, source_rc, source_texture, 0);
m_post_processor->BlitFromTexture(right_rc, source_rc, source_texture, 1); m_post_processor->BlitFromTexture(right_rc, source_rc, source_texture, 1);
} }
// Every other case will be treated the same (stereo or not).
// If there's multiple source layers, they should all be copied.
else else
{ {
m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture);
} }
} }

View File

@ -139,6 +139,15 @@ void VideoConfig::Refresh()
bArbitraryMipmapDetection = Config::Get(Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION); bArbitraryMipmapDetection = Config::Get(Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION);
fArbitraryMipmapDetectionThreshold = fArbitraryMipmapDetectionThreshold =
Config::Get(Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION_THRESHOLD); Config::Get(Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION_THRESHOLD);
bHDR = Config::Get(Config::GFX_ENHANCE_HDR_OUTPUT);
color_correction.bCorrectColorSpace = Config::Get(Config::GFX_CC_CORRECT_COLOR_SPACE);
color_correction.game_color_space = Config::Get(Config::GFX_CC_GAME_COLOR_SPACE);
color_correction.bCorrectGamma = Config::Get(Config::GFX_CC_CORRECT_GAMMA);
color_correction.fGameGamma = Config::Get(Config::GFX_CC_GAME_GAMMA);
color_correction.bSDRDisplayGammaSRGB = Config::Get(Config::GFX_CC_SDR_DISPLAY_GAMMA_SRGB);
color_correction.fSDRDisplayCustomGamma = Config::Get(Config::GFX_CC_SDR_DISPLAY_CUSTOM_GAMMA);
color_correction.fHDRPaperWhiteNits = Config::Get(Config::GFX_CC_HDR_PAPER_WHITE_NITS);
stereo_mode = Config::Get(Config::GFX_STEREO_MODE); stereo_mode = Config::Get(Config::GFX_STEREO_MODE);
iStereoDepth = Config::Get(Config::GFX_STEREO_DEPTH); iStereoDepth = Config::Get(Config::GFX_STEREO_DEPTH);
@ -263,6 +272,7 @@ void CheckForConfigChanges()
const AspectMode old_suggested_aspect_mode = g_ActiveConfig.suggested_aspect_mode; const AspectMode old_suggested_aspect_mode = g_ActiveConfig.suggested_aspect_mode;
const bool old_widescreen_hack = g_ActiveConfig.bWidescreenHack; const bool old_widescreen_hack = g_ActiveConfig.bWidescreenHack;
const auto old_post_processing_shader = g_ActiveConfig.sPostProcessingShader; const auto old_post_processing_shader = g_ActiveConfig.sPostProcessingShader;
const auto old_hdr = g_ActiveConfig.bHDR;
UpdateActiveConfig(); UpdateActiveConfig();
FreeLook::UpdateActiveConfig(); FreeLook::UpdateActiveConfig();
@ -314,6 +324,8 @@ void CheckForConfigChanges()
changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO; changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO;
if (old_post_processing_shader != g_ActiveConfig.sPostProcessingShader) if (old_post_processing_shader != g_ActiveConfig.sPostProcessingShader)
changed_bits |= CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER; changed_bits |= CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER;
if (old_hdr != g_ActiveConfig.bHDR)
changed_bits |= CONFIG_CHANGE_BIT_HDR;
// No changes? // No changes?
if (changed_bits == 0) if (changed_bits == 0)
@ -323,7 +335,7 @@ void CheckForConfigChanges()
// Framebuffer changed? // Framebuffer changed?
if (changed_bits & (CONFIG_CHANGE_BIT_MULTISAMPLES | CONFIG_CHANGE_BIT_STEREO_MODE | if (changed_bits & (CONFIG_CHANGE_BIT_MULTISAMPLES | CONFIG_CHANGE_BIT_STEREO_MODE |
CONFIG_CHANGE_BIT_TARGET_SIZE)) CONFIG_CHANGE_BIT_TARGET_SIZE | CONFIG_CHANGE_BIT_HDR))
{ {
g_framebuffer_manager->RecreateEFBFramebuffer(); g_framebuffer_manager->RecreateEFBFramebuffer();
} }

View File

@ -52,6 +52,13 @@ enum class TextureFilteringMode : int
Linear, Linear,
}; };
enum class ColorCorrectionRegion : int
{
SMPTE_NTSCM,
SYSTEMJ_NTSCJ,
EBU_PAL,
};
enum class TriState : int enum class TriState : int
{ {
Off, Off,
@ -72,6 +79,7 @@ enum ConfigChangeBits : u32
CONFIG_CHANGE_BIT_BBOX = (1 << 7), CONFIG_CHANGE_BIT_BBOX = (1 << 7),
CONFIG_CHANGE_BIT_ASPECT_RATIO = (1 << 8), CONFIG_CHANGE_BIT_ASPECT_RATIO = (1 << 8),
CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER = (1 << 9), CONFIG_CHANGE_BIT_POST_PROCESSING_SHADER = (1 << 9),
CONFIG_CHANGE_BIT_HDR = (1 << 10),
}; };
// NEVER inherit from this class. // NEVER inherit from this class.
@ -101,6 +109,26 @@ struct VideoConfig final
bool bDisableCopyFilter = false; bool bDisableCopyFilter = false;
bool bArbitraryMipmapDetection = false; bool bArbitraryMipmapDetection = false;
float fArbitraryMipmapDetectionThreshold = 0; float fArbitraryMipmapDetectionThreshold = 0;
bool bHDR = false;
// Color Correction
struct
{
// Color Space Correction:
bool bCorrectColorSpace = false;
ColorCorrectionRegion game_color_space = ColorCorrectionRegion::SMPTE_NTSCM;
// Gamma Correction:
bool bCorrectGamma = false;
float fGameGamma = 2.35f;
bool bSDRDisplayGammaSRGB = true;
// Custom gamma when the display is not sRGB
float fSDRDisplayCustomGamma = 2.2f;
// HDR:
// 200 is a good default value that matches the brightness of many SDR screens
float fHDRPaperWhiteNits = 200.f;
} color_correction;
// Information // Information
bool bShowFPS = false; bool bShowFPS = false;
@ -275,6 +303,7 @@ struct VideoConfig final
bool bSupportsDynamicVertexLoader = false; bool bSupportsDynamicVertexLoader = false;
bool bSupportsVSLinePointExpand = false; bool bSupportsVSLinePointExpand = false;
bool bSupportsGLLayerInFS = true; bool bSupportsGLLayerInFS = true;
bool bSupportsHDROutput = false;
} backend_info; } backend_info;
// Utility // Utility