Merge pull request #11999 from Filoppi/post_process_fixes

Video: implement output resampling (upscaling/downscaling) methods
This commit is contained in:
Admiral H. Curtiss
2023-08-18 20:33:09 +02:00
committed by GitHub
10 changed files with 490 additions and 96 deletions

View File

@ -118,6 +118,8 @@ const Info<std::string> GFX_DRIVER_LIB_NAME{{System::GFX, "Settings", "DriverLib
const Info<TextureFilteringMode> GFX_ENHANCE_FORCE_TEXTURE_FILTERING{
{System::GFX, "Enhancements", "ForceTextureFiltering"}, TextureFilteringMode::Default};
const Info<int> GFX_ENHANCE_MAX_ANISOTROPY{{System::GFX, "Enhancements", "MaxAnisotropy"}, 0};
const Info<OutputResamplingMode> GFX_ENHANCE_OUTPUT_RESAMPLING{
{System::GFX, "Enhancements", "OutputResampling"}, OutputResamplingMode::Default};
const Info<std::string> GFX_ENHANCE_POST_SHADER{
{System::GFX, "Enhancements", "PostProcessingShader"}, ""};
const Info<bool> GFX_ENHANCE_FORCE_TRUE_COLOR{{System::GFX, "Enhancements", "ForceTrueColor"},

View File

@ -11,6 +11,7 @@ enum class AspectMode : int;
enum class ShaderCompilationMode : int;
enum class StereoMode : int;
enum class TextureFilteringMode : int;
enum class OutputResamplingMode : int;
enum class ColorCorrectionRegion : int;
enum class TriState : int;
@ -101,6 +102,7 @@ extern const Info<bool> GFX_MODS_ENABLE;
extern const Info<TextureFilteringMode> GFX_ENHANCE_FORCE_TEXTURE_FILTERING;
extern const Info<int> GFX_ENHANCE_MAX_ANISOTROPY; // NOTE - this is x in (1 << x)
extern const Info<OutputResamplingMode> GFX_ENHANCE_OUTPUT_RESAMPLING;
extern const Info<std::string> GFX_ENHANCE_POST_SHADER;
extern const Info<bool> GFX_ENHANCE_FORCE_TRUE_COLOR;
extern const Info<bool> GFX_ENHANCE_DISABLE_COPY_FILTER;

View File

@ -105,6 +105,22 @@ void EnhancementsWidget::CreateWidgets()
m_texture_filtering_combo->addItem(tr("Force Linear and 16x Anisotropic"),
TEXTURE_FILTERING_FORCE_LINEAR_ANISO_16X);
m_output_resampling_combo = new ToolTipComboBox();
m_output_resampling_combo->addItem(tr("Default"),
static_cast<int>(OutputResamplingMode::Default));
m_output_resampling_combo->addItem(tr("Bilinear"),
static_cast<int>(OutputResamplingMode::Bilinear));
m_output_resampling_combo->addItem(tr("Bicubic: B-Spline"),
static_cast<int>(OutputResamplingMode::BSpline));
m_output_resampling_combo->addItem(tr("Bicubic: Mitchell-Netravali"),
static_cast<int>(OutputResamplingMode::MitchellNetravali));
m_output_resampling_combo->addItem(tr("Bicubic: Catmull-Rom"),
static_cast<int>(OutputResamplingMode::CatmullRom));
m_output_resampling_combo->addItem(tr("Sharp Bilinear"),
static_cast<int>(OutputResamplingMode::SharpBilinear));
m_output_resampling_combo->addItem(tr("Area Sampling"),
static_cast<int>(OutputResamplingMode::AreaSampling));
m_configure_color_correction = new ToolTipPushButton(tr("Configure"));
m_pp_effect = new ToolTipComboBox();
@ -136,6 +152,10 @@ void EnhancementsWidget::CreateWidgets()
enhancements_layout->addWidget(m_texture_filtering_combo, row, 1, 1, -1);
++row;
enhancements_layout->addWidget(new QLabel(tr("Output Resampling:")), row, 0);
enhancements_layout->addWidget(m_output_resampling_combo, row, 1, 1, -1);
++row;
enhancements_layout->addWidget(new QLabel(tr("Color Correction:")), row, 0);
enhancements_layout->addWidget(m_configure_color_correction, row, 1, 1, -1);
++row;
@ -195,6 +215,8 @@ void EnhancementsWidget::ConnectWidgets()
[this](int) { SaveSettings(); });
connect(m_texture_filtering_combo, qOverload<int>(&QComboBox::currentIndexChanged),
[this](int) { SaveSettings(); });
connect(m_output_resampling_combo, qOverload<int>(&QComboBox::currentIndexChanged),
[this](int) { SaveSettings(); });
connect(m_pp_effect, qOverload<int>(&QComboBox::currentIndexChanged),
[this](int) { SaveSettings(); });
connect(m_3d_mode, qOverload<int>(&QComboBox::currentIndexChanged), [this] {
@ -325,6 +347,14 @@ void EnhancementsWidget::LoadSettings()
break;
}
// Resampling
const OutputResamplingMode output_resampling_mode =
Config::Get(Config::GFX_ENHANCE_OUTPUT_RESAMPLING);
m_output_resampling_combo->setCurrentIndex(static_cast<int>(output_resampling_mode));
m_output_resampling_combo->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
// Color Correction
m_configure_color_correction->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
// Post Processing Shader
@ -413,6 +443,10 @@ void EnhancementsWidget::SaveSettings()
break;
}
const int output_resampling_selection = m_output_resampling_combo->currentData().toInt();
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_OUTPUT_RESAMPLING,
static_cast<OutputResamplingMode>(output_resampling_selection));
const bool anaglyph = g_Config.stereo_mode == StereoMode::Anaglyph;
const bool passive = g_Config.stereo_mode == StereoMode::Passive;
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_POST_SHADER,
@ -455,6 +489,37 @@ void EnhancementsWidget::AddDescriptions()
"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 "
"games.<br><br><dolphin_emphasis>If unsure, select 'Default'.</dolphin_emphasis>");
static const char TR_OUTPUT_RESAMPLING_DESCRIPTION[] =
QT_TR_NOOP("Affects how the game output is scaled to the window resolution."
"<br>The performance mostly depends on the number of samples each method uses."
"<br>Compared to SSAA, resampling is useful in case the output window"
"<br>resolution isn't a multiplier of the native emulation resolution."
"<br><br><b>Default</b> - [fastest]"
"<br>Internal GPU bilinear sampler which is not gamma corrected."
"<br>This setting might be ignored if gamma correction is forced on."
"<br><br><b>Bilinear</b> - [4 samples]"
"<br>Gamma corrected linear interpolation between pixels."
"<br><br><b>Bicubic</b> - [16 samples]"
"<br>Gamma corrected cubic interpolation between pixels."
"<br>Good when rescaling between close resolutions. i.e 1080p and 1440p."
"<br>Comes in various flavors:"
"<br><b>B-Spline</b>: Blurry, but avoids all lobing artifacts"
"<br><b>Mitchell-Netravali</b>: Good middle ground between blurry and lobing"
"<br><b>Catmull-Rom</b>: Sharper, but can cause lobing artifacts"
"<br><br><b>Sharp Bilinear</b> - [1-4 samples]"
"<br>Similarly to \"Nearest Neighbor\", it maintains a sharp look,"
"<br>but also does some blending to avoid shimmering."
"<br>Works best with 2D games at low resolutions."
"<br><br><b>Area Sampling</b> - [up to 324 samples]"
"<br>Weights pixels by the percentage of area they occupy. Gamma corrected."
"<br>Best for down scaling by more than 2x."
"<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.");
@ -537,6 +602,9 @@ void EnhancementsWidget::AddDescriptions()
m_texture_filtering_combo->SetTitle(tr("Texture Filtering"));
m_texture_filtering_combo->SetDescription(tr(TR_FORCE_TEXTURE_FILTERING_DESCRIPTION));
m_output_resampling_combo->SetTitle(tr("Output Resampling"));
m_output_resampling_combo->SetDescription(tr(TR_OUTPUT_RESAMPLING_DESCRIPTION));
m_configure_color_correction->SetTitle(tr("Color Correction"));
m_configure_color_correction->SetDescription(tr(TR_COLOR_CORRECTION_DESCRIPTION));

View File

@ -39,6 +39,7 @@ private:
ConfigChoice* m_ir_combo;
ToolTipComboBox* m_aa_combo;
ToolTipComboBox* m_texture_filtering_combo;
ToolTipComboBox* m_output_resampling_combo;
ToolTipComboBox* m_pp_effect;
ToolTipPushButton* m_configure_color_correction;
QPushButton* m_configure_pp_effect;

View File

@ -419,9 +419,9 @@ std::vector<std::string> PostProcessing::GetPassiveShaderList()
bool PostProcessing::Initialize(AbstractTextureFormat format)
{
m_framebuffer_format = format;
// CompilePixelShader must be run first if configuration options are used.
// CompilePixelShader() must be run first if configuration options are used.
// Otherwise the UBO has a different member list between vertex and pixel
// shaders, which is a link error.
// shaders, which is a link error on some backends.
if (!CompilePixelShader() || !CompileVertexShader() || !CompilePipeline())
return false;
@ -486,23 +486,29 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
MathUtil::Rectangle<int> src_rect = src;
g_gfx->SetSamplerState(0, RenderState::GetLinearSamplerState());
g_gfx->SetSamplerState(1, RenderState::GetPointSamplerState());
g_gfx->SetTexture(0, src_tex);
g_gfx->SetTexture(1, src_tex);
const bool is_color_correction_active = IsColorCorrectionActive();
const bool needs_color_correction = IsColorCorrectionActive();
// Rely on the default (bi)linear sampler with the default mode
// (it might not be gamma corrected).
const bool needs_resampling =
g_ActiveConfig.output_resampling_mode > OutputResamplingMode::Default;
const bool needs_intermediary_buffer = NeedsIntermediaryBuffer();
const bool needs_default_pipeline = needs_color_correction || needs_resampling;
const AbstractPipeline* final_pipeline = m_pipeline.get();
std::vector<u8>* uniform_staging_buffer = &m_default_uniform_staging_buffer;
bool default_uniform_staging_buffer = true;
const MathUtil::Rectangle<int> present_rect = g_presenter->GetTargetRectangle();
// Intermediary pass.
// We draw to a high quality intermediary texture for two reasons:
// We draw to a high quality intermediary texture for a couple reasons:
// -Consistently do high quality gamma corrected resampling (upscaling/downscaling)
// -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)
// -Keep the post process phase in linear space, to better operate with colors
if (m_default_pipeline && needs_default_pipeline && needs_intermediary_buffer)
{
AbstractFramebuffer* const previous_framebuffer = g_gfx->GetCurrentFramebuffer();
@ -512,13 +518,18 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
// so it would be a waste to allocate two layers (see "bUsesExplictQuadBuffering").
const u32 target_layers = copy_all_layers ? src_tex->GetLayers() : 1;
const u32 target_width =
needs_resampling ? present_rect.GetWidth() : static_cast<u32>(src_rect.GetWidth());
const u32 target_height =
needs_resampling ? present_rect.GetHeight() : static_cast<u32>(src_rect.GetHeight());
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()->GetWidth() != target_width ||
m_intermediary_color_texture.get()->GetHeight() != target_height ||
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(),
target_width, target_height, 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");
@ -530,8 +541,8 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
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);
present_rect, uniform_staging_buffer->data(), !default_uniform_staging_buffer,
true);
g_vertex_manager->UploadUtilityUniforms(uniform_staging_buffer->data(),
static_cast<u32>(uniform_staging_buffer->size()));
@ -544,6 +555,7 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
src_rect = m_intermediary_color_texture->GetRect();
src_tex = m_intermediary_color_texture.get();
g_gfx->SetTexture(0, src_tex);
g_gfx->SetTexture(1, 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,
@ -557,7 +569,7 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
// 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)
if (m_default_pipeline && needs_default_pipeline)
{
final_pipeline = m_default_pipeline.get();
}
@ -580,8 +592,8 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
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);
present_rect, uniform_staging_buffer->data(), !default_uniform_staging_buffer,
false);
g_vertex_manager->UploadUtilityUniforms(uniform_staging_buffer->data(),
static_cast<u32>(uniform_staging_buffer->size()));
@ -609,7 +621,11 @@ std::string PostProcessing::GetUniformBufferHeader(bool user_post_process) const
// The first (but not necessarily only) source layer we target
ss << " int src_layer;\n";
ss << " uint time;\n";
ss << " int graphics_api;\n";
// If true, it's an intermediary buffer (including the first), if false, it's the final one
ss << " int intermediary_buffer;\n";
ss << " int resampling_method;\n";
ss << " int correct_color_space;\n";
ss << " int game_color_space;\n";
ss << " int correct_gamma;\n";
@ -742,6 +758,7 @@ void SetOutput(float4 color)
#define GetOption(x) (x)
#define OptionEnabled(x) ((x) != 0)
#define OptionDisabled(x) ((x) == 0)
)";
return ss.str();
@ -752,13 +769,9 @@ std::string PostProcessing::GetFooter() const
return {};
}
bool PostProcessing::CompileVertexShader()
std::string GetVertexShaderBody()
{
std::ostringstream ss;
// 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)
{
ss << "VARYING_LOCATION(0) out VertexData {\n";
@ -779,21 +792,34 @@ bool PostProcessing::CompileVertexShader()
// Vulkan Y needs to be inverted on every pass
if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan)
{
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";
}
// OpenGL Y needs to be inverted in all passes except the last one
else if (g_ActiveConfig.backend_info.api_type == APIType::OpenGL)
{
ss << " if (intermediary_buffer != 0)\n";
ss << " opos.y = -opos.y;\n";
}
ss << "}\n";
return ss.str();
}
bool PostProcessing::CompileVertexShader()
{
std::ostringstream ss_default;
ss_default << GetUniformBufferHeader(false);
ss_default << GetVertexShaderBody();
m_default_vertex_shader = g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss_default.str(),
"Default post-processing vertex shader");
std::ostringstream ss;
ss << GetUniformBufferHeader(true);
ss << GetVertexShaderBody();
m_vertex_shader =
g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss.str(), "Post-processing vertex shader");
if (!m_default_vertex_shader || !m_vertex_shader)
{
PanicAlertFmt("Failed to compile post-processing vertex shader");
@ -816,6 +842,9 @@ struct BuiltinUniforms
std::array<float, 4> src_rect;
s32 src_layer;
u32 time;
s32 graphics_api;
s32 intermediary_buffer;
s32 resampling_method;
s32 correct_color_space;
s32 game_color_space;
s32 correct_gamma;
@ -839,7 +868,7 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
const AbstractTexture* src_tex, int src_layer,
const MathUtil::Rectangle<int>& dst,
const MathUtil::Rectangle<int>& wnd, u8* buffer,
bool user_post_process)
bool user_post_process, bool intermediary_buffer)
{
const float rcp_src_width = 1.0f / src_tex->GetWidth();
const float rcp_src_height = 1.0f / src_tex->GetHeight();
@ -860,7 +889,10 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
static_cast<float>(src.GetHeight()) * rcp_src_height};
builtin_uniforms.src_layer = static_cast<s32>(src_layer);
builtin_uniforms.time = static_cast<u32>(m_timer.ElapsedMs());
builtin_uniforms.graphics_api = static_cast<s32>(g_ActiveConfig.backend_info.api_type);
builtin_uniforms.intermediary_buffer = static_cast<s32>(intermediary_buffer);
builtin_uniforms.resampling_method = static_cast<s32>(g_ActiveConfig.output_resampling_mode);
// Color correction related uniforms.
// These are mainly used by the "m_default_pixel_shader",
// but should also be accessible to all other shaders.
@ -883,6 +915,8 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
std::memcpy(buffer, &builtin_uniforms, sizeof(builtin_uniforms));
buffer += sizeof(builtin_uniforms);
// Don't include the custom pp shader options if they are not necessary,
// having mismatching uniforms between different shaders can cause issues on some backends
if (!user_post_process)
return;
@ -1000,8 +1034,7 @@ bool PostProcessing::CompilePipeline()
const bool needs_intermediary_buffer = NeedsIntermediaryBuffer();
AbstractPipelineConfig config = {};
config.vertex_shader =
needs_intermediary_buffer ? m_vertex_shader.get() : m_default_vertex_shader.get();
config.vertex_shader = m_default_vertex_shader.get();
// This geometry shader will take care of reading both layer 0 and 1 on the source texture,
// and writing to both layer 0 and 1 on the render target.
config.geometry_shader = UseGeometryShaderForPostProcess(needs_intermediary_buffer) ?
@ -1018,7 +1051,7 @@ bool PostProcessing::CompilePipeline()
if (config.pixel_shader)
m_default_pipeline = g_gfx->CreatePipeline(config);
config.vertex_shader = m_default_vertex_shader.get();
config.vertex_shader = m_vertex_shader.get();
config.geometry_shader = UseGeometryShaderForPostProcess(false) ?
g_shader_cache->GetTexcoordGeometryShader() :
nullptr;

View File

@ -124,7 +124,8 @@ protected:
size_t CalculateUniformsSize(bool user_post_process) const;
void FillUniformBuffer(const MathUtil::Rectangle<int>& src, const AbstractTexture* src_tex,
int src_layer, const MathUtil::Rectangle<int>& dst,
const MathUtil::Rectangle<int>& wnd, u8* buffer, bool user_post_process);
const MathUtil::Rectangle<int>& wnd, u8* buffer, bool user_post_process,
bool intermediary_buffer);
// Timer for determining our time value
Common::Timer m_timer;

View File

@ -133,6 +133,7 @@ void VideoConfig::Refresh()
texture_filtering_mode = Config::Get(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING);
iMaxAnisotropy = Config::Get(Config::GFX_ENHANCE_MAX_ANISOTROPY);
output_resampling_mode = Config::Get(Config::GFX_ENHANCE_OUTPUT_RESAMPLING);
sPostProcessingShader = Config::Get(Config::GFX_ENHANCE_POST_SHADER);
bForceTrueColor = Config::Get(Config::GFX_ENHANCE_FORCE_TRUE_COLOR);
bDisableCopyFilter = Config::Get(Config::GFX_ENHANCE_DISABLE_COPY_FILTER);

View File

@ -52,6 +52,17 @@ enum class TextureFilteringMode : int
Linear,
};
enum class OutputResamplingMode : int
{
Default,
Bilinear,
BSpline,
MitchellNetravali,
CatmullRom,
SharpBilinear,
AreaSampling,
};
enum class ColorCorrectionRegion : int
{
SMPTE_NTSCM,
@ -103,6 +114,7 @@ struct VideoConfig final
bool bSSAA = false;
int iEFBScale = 0;
TextureFilteringMode texture_filtering_mode = TextureFilteringMode::Default;
OutputResamplingMode output_resampling_mode = OutputResamplingMode::Default;
int iMaxAnisotropy = 0;
std::string sPostProcessingShader;
bool bForceTrueColor = false;