2015-05-24 04:55:12 +00:00
|
|
|
// Copyright 2014 Dolphin Emulator Project
|
2021-07-05 01:22:19 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2014-07-29 16:47:56 +00:00
|
|
|
|
2019-07-17 00:18:48 +00:00
|
|
|
#include "VideoCommon/PostProcessing.h"
|
|
|
|
|
2016-01-17 21:54:31 +00:00
|
|
|
#include <sstream>
|
2014-07-29 16:47:56 +00:00
|
|
|
#include <string>
|
2019-07-08 11:35:53 +00:00
|
|
|
#include <string_view>
|
2014-07-29 16:47:56 +00:00
|
|
|
|
2019-11-22 22:10:41 +00:00
|
|
|
#include <fmt/format.h>
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
#include "Common/Assert.h"
|
2014-07-29 16:47:56 +00:00
|
|
|
#include "Common/CommonPaths.h"
|
2016-01-17 21:54:31 +00:00
|
|
|
#include "Common/CommonTypes.h"
|
2017-04-21 13:59:45 +00:00
|
|
|
#include "Common/FileSearch.h"
|
2014-07-29 16:47:56 +00:00
|
|
|
#include "Common/FileUtil.h"
|
|
|
|
#include "Common/IniFile.h"
|
2016-01-17 21:54:31 +00:00
|
|
|
#include "Common/Logging/Log.h"
|
2019-02-15 01:59:50 +00:00
|
|
|
#include "Common/MsgHandler.h"
|
2014-07-29 16:47:56 +00:00
|
|
|
#include "Common/StringUtil.h"
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
#include "VideoCommon/AbstractFramebuffer.h"
|
|
|
|
#include "VideoCommon/AbstractPipeline.h"
|
|
|
|
#include "VideoCommon/AbstractShader.h"
|
|
|
|
#include "VideoCommon/AbstractTexture.h"
|
|
|
|
#include "VideoCommon/FramebufferManager.h"
|
|
|
|
#include "VideoCommon/RenderBase.h"
|
|
|
|
#include "VideoCommon/ShaderCache.h"
|
|
|
|
#include "VideoCommon/VertexManagerBase.h"
|
2019-12-22 18:40:40 +00:00
|
|
|
#include "VideoCommon/VideoCommon.h"
|
2014-07-29 16:47:56 +00:00
|
|
|
#include "VideoCommon/VideoConfig.h"
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
namespace VideoCommon
|
2017-04-21 13:59:45 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
static const char s_default_shader[] = "void main() { SetOutput(Sample()); }\n";
|
2017-04-21 13:59:45 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
PostProcessingConfiguration::PostProcessingConfiguration() = default;
|
2018-05-21 15:54:53 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
PostProcessingConfiguration::~PostProcessingConfiguration() = default;
|
2018-05-21 15:54:53 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void PostProcessingConfiguration::LoadShader(const std::string& shader)
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
|
|
|
// Load the shader from the configuration if there isn't one sent to us.
|
|
|
|
m_current_shader = shader;
|
2019-02-15 01:59:50 +00:00
|
|
|
if (shader.empty())
|
|
|
|
{
|
|
|
|
LoadDefaultShader();
|
|
|
|
return;
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2019-05-02 02:47:50 +00:00
|
|
|
std::string sub_dir = "";
|
|
|
|
|
|
|
|
if (g_Config.stereo_mode == StereoMode::Anaglyph)
|
|
|
|
{
|
|
|
|
sub_dir = ANAGLYPH_DIR DIR_SEP;
|
|
|
|
}
|
|
|
|
else if (g_Config.stereo_mode == StereoMode::Passive)
|
|
|
|
{
|
|
|
|
sub_dir = PASSIVE_DIR DIR_SEP;
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
// loading shader code
|
|
|
|
std::string code;
|
2015-01-03 00:33:30 +00:00
|
|
|
std::string path = File::GetUserPath(D_SHADERS_IDX) + sub_dir + shader + ".glsl";
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
if (!File::Exists(path))
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
// Fallback to shared user dir
|
|
|
|
path = File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir + shader + ".glsl";
|
2014-07-29 16:47:56 +00:00
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
if (!File::ReadFileToString(path, code))
|
|
|
|
{
|
2020-11-14 03:33:26 +00:00
|
|
|
ERROR_LOG_FMT(VIDEO, "Post-processing shader not found: {}", path);
|
2019-02-15 01:59:50 +00:00
|
|
|
LoadDefaultShader();
|
|
|
|
return;
|
2014-07-29 16:47:56 +00:00
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
LoadOptions(code);
|
|
|
|
LoadOptionsConfiguration();
|
2019-02-15 01:59:50 +00:00
|
|
|
m_current_shader_code = code;
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void PostProcessingConfiguration::LoadDefaultShader()
|
|
|
|
{
|
|
|
|
m_options.clear();
|
|
|
|
m_any_options_dirty = false;
|
|
|
|
m_current_shader_code = s_default_shader;
|
2014-07-29 16:47:56 +00:00
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void PostProcessingConfiguration::LoadOptions(const std::string& code)
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
|
|
|
const std::string config_start_delimiter = "[configuration]";
|
|
|
|
const std::string config_end_delimiter = "[/configuration]";
|
|
|
|
size_t configuration_start = code.find(config_start_delimiter);
|
|
|
|
size_t configuration_end = code.find(config_end_delimiter);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
m_options.clear();
|
|
|
|
m_any_options_dirty = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
if (configuration_start == std::string::npos || configuration_end == std::string::npos)
|
|
|
|
{
|
|
|
|
// Issue loading configuration or there isn't one.
|
|
|
|
return;
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
std::string configuration_string =
|
|
|
|
code.substr(configuration_start + config_start_delimiter.size(),
|
|
|
|
configuration_end - configuration_start - config_start_delimiter.size());
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
std::istringstream in(configuration_string);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
struct GLSLStringOption
|
|
|
|
{
|
|
|
|
std::string m_type;
|
|
|
|
std::vector<std::pair<std::string, std::string>> m_options;
|
|
|
|
};
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
std::vector<GLSLStringOption> option_strings;
|
|
|
|
GLSLStringOption* current_strings = nullptr;
|
|
|
|
while (!in.eof())
|
|
|
|
{
|
2019-07-08 11:35:53 +00:00
|
|
|
std::string line_str;
|
|
|
|
if (std::getline(in, line_str))
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
2019-07-08 11:35:53 +00:00
|
|
|
std::string_view line = line_str;
|
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
#ifndef _WIN32
|
|
|
|
// Check for CRLF eol and convert it to LF
|
|
|
|
if (!line.empty() && line.at(line.size() - 1) == '\r')
|
2019-07-08 11:35:53 +00:00
|
|
|
line.remove_suffix(1);
|
2014-07-29 16:47:56 +00:00
|
|
|
#endif
|
|
|
|
|
2019-02-12 22:47:17 +00:00
|
|
|
if (!line.empty())
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
|
|
|
if (line[0] == '[')
|
|
|
|
{
|
|
|
|
size_t endpos = line.find("]");
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
if (endpos != std::string::npos)
|
|
|
|
{
|
|
|
|
// New section!
|
2019-07-08 11:35:53 +00:00
|
|
|
std::string_view sub = line.substr(1, endpos - 1);
|
|
|
|
option_strings.push_back({std::string(sub)});
|
2014-07-29 16:47:56 +00:00
|
|
|
current_strings = &option_strings.back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (current_strings)
|
|
|
|
{
|
|
|
|
std::string key, value;
|
|
|
|
IniFile::ParseLine(line, &key, &value);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2019-02-12 22:47:17 +00:00
|
|
|
if (!(key.empty() && value.empty()))
|
2015-02-04 15:36:42 +00:00
|
|
|
current_strings->m_options.emplace_back(key, value);
|
2014-07-29 16:47:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
for (const auto& it : option_strings)
|
|
|
|
{
|
|
|
|
ConfigurationOption option;
|
|
|
|
option.m_dirty = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
if (it.m_type == "OptionBool")
|
|
|
|
option.m_type = ConfigurationOption::OptionType::OPTION_BOOL;
|
|
|
|
else if (it.m_type == "OptionRangeFloat")
|
|
|
|
option.m_type = ConfigurationOption::OptionType::OPTION_FLOAT;
|
|
|
|
else if (it.m_type == "OptionRangeInteger")
|
|
|
|
option.m_type = ConfigurationOption::OptionType::OPTION_INTEGER;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
for (const auto& string_option : it.m_options)
|
|
|
|
{
|
|
|
|
if (string_option.first == "GUIName")
|
|
|
|
{
|
|
|
|
option.m_gui_name = string_option.second;
|
|
|
|
}
|
|
|
|
else if (string_option.first == "OptionName")
|
|
|
|
{
|
|
|
|
option.m_option_name = string_option.second;
|
|
|
|
}
|
|
|
|
else if (string_option.first == "DependentOption")
|
|
|
|
{
|
|
|
|
option.m_dependent_option = string_option.second;
|
|
|
|
}
|
|
|
|
else if (string_option.first == "MinValue" || string_option.first == "MaxValue" ||
|
|
|
|
string_option.first == "DefaultValue" || string_option.first == "StepAmount")
|
|
|
|
{
|
|
|
|
std::vector<s32>* output_integer = nullptr;
|
|
|
|
std::vector<float>* output_float = nullptr;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
if (string_option.first == "MinValue")
|
|
|
|
{
|
|
|
|
output_integer = &option.m_integer_min_values;
|
|
|
|
output_float = &option.m_float_min_values;
|
|
|
|
}
|
|
|
|
else if (string_option.first == "MaxValue")
|
|
|
|
{
|
|
|
|
output_integer = &option.m_integer_max_values;
|
|
|
|
output_float = &option.m_float_max_values;
|
|
|
|
}
|
|
|
|
else if (string_option.first == "DefaultValue")
|
|
|
|
{
|
|
|
|
output_integer = &option.m_integer_values;
|
|
|
|
output_float = &option.m_float_values;
|
|
|
|
}
|
|
|
|
else if (string_option.first == "StepAmount")
|
|
|
|
{
|
|
|
|
output_integer = &option.m_integer_step_values;
|
|
|
|
output_float = &option.m_float_step_values;
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
if (option.m_type == ConfigurationOption::OptionType::OPTION_BOOL)
|
|
|
|
{
|
|
|
|
TryParse(string_option.second, &option.m_bool_value);
|
|
|
|
}
|
|
|
|
else if (option.m_type == ConfigurationOption::OptionType::OPTION_INTEGER)
|
|
|
|
{
|
|
|
|
TryParseVector(string_option.second, output_integer);
|
|
|
|
if (output_integer->size() > 4)
|
|
|
|
output_integer->erase(output_integer->begin() + 4, output_integer->end());
|
|
|
|
}
|
|
|
|
else if (option.m_type == ConfigurationOption::OptionType::OPTION_FLOAT)
|
|
|
|
{
|
|
|
|
TryParseVector(string_option.second, output_float);
|
|
|
|
if (output_float->size() > 4)
|
|
|
|
output_float->erase(output_float->begin() + 4, output_float->end());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_options[option.m_option_name] = option;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void PostProcessingConfiguration::LoadOptionsConfiguration()
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
|
|
|
IniFile ini;
|
|
|
|
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
|
|
|
|
std::string section = m_current_shader + "-options";
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
for (auto& it : m_options)
|
|
|
|
{
|
|
|
|
switch (it.second.m_type)
|
|
|
|
{
|
|
|
|
case ConfigurationOption::OptionType::OPTION_BOOL:
|
|
|
|
ini.GetOrCreateSection(section)->Get(it.second.m_option_name, &it.second.m_bool_value,
|
|
|
|
it.second.m_bool_value);
|
|
|
|
break;
|
|
|
|
case ConfigurationOption::OptionType::OPTION_INTEGER:
|
|
|
|
{
|
|
|
|
std::string value;
|
|
|
|
ini.GetOrCreateSection(section)->Get(it.second.m_option_name, &value);
|
2019-02-12 22:47:17 +00:00
|
|
|
if (!value.empty())
|
2014-07-29 16:47:56 +00:00
|
|
|
TryParseVector(value, &it.second.m_integer_values);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ConfigurationOption::OptionType::OPTION_FLOAT:
|
|
|
|
{
|
|
|
|
std::string value;
|
|
|
|
ini.GetOrCreateSection(section)->Get(it.second.m_option_name, &value);
|
2019-02-12 22:47:17 +00:00
|
|
|
if (!value.empty())
|
2014-07-29 16:47:56 +00:00
|
|
|
TryParseVector(value, &it.second.m_float_values);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void PostProcessingConfiguration::SaveOptionsConfiguration()
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
|
|
|
IniFile ini;
|
|
|
|
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
|
|
|
|
std::string section = m_current_shader + "-options";
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
for (auto& it : m_options)
|
|
|
|
{
|
|
|
|
switch (it.second.m_type)
|
|
|
|
{
|
|
|
|
case ConfigurationOption::OptionType::OPTION_BOOL:
|
|
|
|
{
|
|
|
|
ini.GetOrCreateSection(section)->Set(it.second.m_option_name, it.second.m_bool_value);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ConfigurationOption::OptionType::OPTION_INTEGER:
|
|
|
|
{
|
2019-03-02 18:42:25 +00:00
|
|
|
std::string value;
|
2014-07-29 16:47:56 +00:00
|
|
|
for (size_t i = 0; i < it.second.m_integer_values.size(); ++i)
|
2019-11-22 22:10:41 +00:00
|
|
|
{
|
|
|
|
value += fmt::format("{}{}", it.second.m_integer_values[i],
|
|
|
|
i == (it.second.m_integer_values.size() - 1) ? "" : ", ");
|
|
|
|
}
|
2014-07-29 16:47:56 +00:00
|
|
|
ini.GetOrCreateSection(section)->Set(it.second.m_option_name, value);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ConfigurationOption::OptionType::OPTION_FLOAT:
|
|
|
|
{
|
2015-01-20 22:40:46 +00:00
|
|
|
std::ostringstream value;
|
|
|
|
value.imbue(std::locale("C"));
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2014-07-29 16:47:56 +00:00
|
|
|
for (size_t i = 0; i < it.second.m_float_values.size(); ++i)
|
2015-01-20 22:40:46 +00:00
|
|
|
{
|
|
|
|
value << it.second.m_float_values[i];
|
|
|
|
if (i != (it.second.m_float_values.size() - 1))
|
|
|
|
value << ", ";
|
|
|
|
}
|
|
|
|
ini.GetOrCreateSection(section)->Set(it.second.m_option_name, value.str());
|
2014-07-29 16:47:56 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX));
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void PostProcessingConfiguration::SetOptionf(const std::string& option, int index, float value)
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
|
|
|
auto it = m_options.find(option);
|
|
|
|
|
|
|
|
it->second.m_float_values[index] = value;
|
|
|
|
it->second.m_dirty = true;
|
|
|
|
m_any_options_dirty = true;
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void PostProcessingConfiguration::SetOptioni(const std::string& option, int index, s32 value)
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
|
|
|
auto it = m_options.find(option);
|
|
|
|
|
|
|
|
it->second.m_integer_values[index] = value;
|
|
|
|
it->second.m_dirty = true;
|
|
|
|
m_any_options_dirty = true;
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void PostProcessingConfiguration::SetOptionb(const std::string& option, bool value)
|
2014-07-29 16:47:56 +00:00
|
|
|
{
|
|
|
|
auto it = m_options.find(option);
|
|
|
|
|
|
|
|
it->second.m_bool_value = value;
|
|
|
|
it->second.m_dirty = true;
|
|
|
|
m_any_options_dirty = true;
|
|
|
|
}
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
PostProcessing::PostProcessing()
|
|
|
|
{
|
|
|
|
m_timer.Start();
|
|
|
|
}
|
|
|
|
|
|
|
|
PostProcessing::~PostProcessing()
|
|
|
|
{
|
|
|
|
m_timer.Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::vector<std::string> GetShaders(const std::string& sub_dir = "")
|
|
|
|
{
|
|
|
|
std::vector<std::string> paths =
|
|
|
|
Common::DoFileSearch({File::GetUserPath(D_SHADERS_IDX) + sub_dir,
|
|
|
|
File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir},
|
|
|
|
{".glsl"});
|
|
|
|
std::vector<std::string> result;
|
|
|
|
for (std::string path : paths)
|
|
|
|
{
|
|
|
|
std::string name;
|
|
|
|
SplitPath(path, nullptr, &name, nullptr);
|
|
|
|
result.push_back(name);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> PostProcessing::GetShaderList()
|
|
|
|
{
|
|
|
|
return GetShaders();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> PostProcessing::GetAnaglyphShaderList()
|
|
|
|
{
|
|
|
|
return GetShaders(ANAGLYPH_DIR DIR_SEP);
|
|
|
|
}
|
|
|
|
|
2019-05-02 02:47:50 +00:00
|
|
|
std::vector<std::string> PostProcessing::GetPassiveShaderList()
|
|
|
|
{
|
|
|
|
return GetShaders(PASSIVE_DIR DIR_SEP);
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
bool PostProcessing::Initialize(AbstractTextureFormat format)
|
|
|
|
{
|
|
|
|
m_framebuffer_format = format;
|
2021-07-07 19:26:46 +00:00
|
|
|
// 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.
|
|
|
|
if (!CompilePixelShader() || !CompileVertexShader() || !CompilePipeline())
|
2019-02-15 01:59:50 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PostProcessing::RecompileShader()
|
|
|
|
{
|
|
|
|
m_pipeline.reset();
|
|
|
|
m_pixel_shader.reset();
|
|
|
|
if (!CompilePixelShader())
|
|
|
|
return;
|
2021-07-18 03:03:26 +00:00
|
|
|
if (!CompileVertexShader())
|
|
|
|
return;
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
CompilePipeline();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PostProcessing::RecompilePipeline()
|
|
|
|
{
|
|
|
|
m_pipeline.reset();
|
|
|
|
CompilePipeline();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|
|
|
const MathUtil::Rectangle<int>& src,
|
|
|
|
const AbstractTexture* src_tex, int src_layer)
|
|
|
|
{
|
|
|
|
if (g_renderer->GetCurrentFramebuffer()->GetColorFormat() != m_framebuffer_format)
|
|
|
|
{
|
|
|
|
m_framebuffer_format = g_renderer->GetCurrentFramebuffer()->GetColorFormat();
|
|
|
|
RecompilePipeline();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_pipeline)
|
|
|
|
return;
|
|
|
|
|
|
|
|
FillUniformBuffer(src, src_tex, src_layer);
|
|
|
|
g_vertex_manager->UploadUtilityUniforms(m_uniform_staging_buffer.data(),
|
|
|
|
static_cast<u32>(m_uniform_staging_buffer.size()));
|
|
|
|
|
|
|
|
g_renderer->SetViewportAndScissor(
|
|
|
|
g_renderer->ConvertFramebufferRectangle(dst, g_renderer->GetCurrentFramebuffer()));
|
|
|
|
g_renderer->SetPipeline(m_pipeline.get());
|
|
|
|
g_renderer->SetTexture(0, src_tex);
|
|
|
|
g_renderer->SetSamplerState(0, RenderState::GetLinearSamplerState());
|
|
|
|
g_renderer->Draw(0, 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string PostProcessing::GetUniformBufferHeader() const
|
|
|
|
{
|
2019-09-14 20:40:34 +00:00
|
|
|
std::ostringstream ss;
|
2019-02-15 01:59:50 +00:00
|
|
|
u32 unused_counter = 1;
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::D3D)
|
|
|
|
ss << "cbuffer PSBlock : register(b0) {\n";
|
|
|
|
else
|
|
|
|
ss << "UBO_BINDING(std140, 1) uniform PSBlock {\n";
|
|
|
|
|
|
|
|
// Builtin uniforms
|
|
|
|
ss << " float4 resolution;\n";
|
2019-05-02 02:47:50 +00:00
|
|
|
ss << " float4 window_resolution;\n";
|
2019-02-15 01:59:50 +00:00
|
|
|
ss << " float4 src_rect;\n";
|
2019-10-02 01:52:52 +00:00
|
|
|
ss << " int src_layer;\n";
|
2019-02-15 01:59:50 +00:00
|
|
|
ss << " uint time;\n";
|
|
|
|
for (u32 i = 0; i < 2; i++)
|
|
|
|
ss << " uint ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
ss << "\n";
|
|
|
|
|
|
|
|
// Custom options/uniforms
|
|
|
|
for (const auto& it : m_config.GetOptions())
|
|
|
|
{
|
|
|
|
if (it.second.m_type ==
|
|
|
|
PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_BOOL)
|
|
|
|
{
|
2019-11-22 22:10:41 +00:00
|
|
|
ss << fmt::format(" int {};\n", it.first);
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < 3; i++)
|
|
|
|
ss << " int ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
}
|
|
|
|
else if (it.second.m_type ==
|
|
|
|
PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER)
|
|
|
|
{
|
|
|
|
u32 count = static_cast<u32>(it.second.m_integer_values.size());
|
|
|
|
if (count == 1)
|
2019-11-22 22:10:41 +00:00
|
|
|
ss << fmt::format(" int {};\n", it.first);
|
2019-02-15 01:59:50 +00:00
|
|
|
else
|
2019-11-22 22:10:41 +00:00
|
|
|
ss << fmt::format(" int{} {};\n", count, it.first);
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
for (u32 i = count; i < 4; i++)
|
|
|
|
ss << " int ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
}
|
|
|
|
else if (it.second.m_type ==
|
|
|
|
PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_FLOAT)
|
|
|
|
{
|
|
|
|
u32 count = static_cast<u32>(it.second.m_float_values.size());
|
|
|
|
if (count == 1)
|
2019-11-22 22:10:41 +00:00
|
|
|
ss << fmt::format(" float {};\n", it.first);
|
2019-02-15 01:59:50 +00:00
|
|
|
else
|
2019-11-22 22:10:41 +00:00
|
|
|
ss << fmt::format(" float{} {};\n", count, it.first);
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
for (u32 i = count; i < 4; i++)
|
|
|
|
ss << " float ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ss << "};\n\n";
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string PostProcessing::GetHeader() const
|
|
|
|
{
|
2019-09-14 20:40:34 +00:00
|
|
|
std::ostringstream ss;
|
2019-02-15 01:59:50 +00:00
|
|
|
ss << GetUniformBufferHeader();
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::D3D)
|
|
|
|
{
|
|
|
|
ss << "Texture2DArray samp0 : register(t0);\n";
|
|
|
|
ss << "SamplerState samp0_ss : register(s0);\n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ss << "SAMPLER_BINDING(0) uniform sampler2DArray samp0;\n";
|
2019-10-02 02:59:50 +00:00
|
|
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
|
|
|
|
{
|
|
|
|
ss << "VARYING_LOCATION(0) in VertexData {\n";
|
|
|
|
ss << " float3 v_tex0;\n";
|
|
|
|
ss << "};\n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ss << "VARYING_LOCATION(0) in float3 v_tex0;\n";
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
ss << "FRAGMENT_OUTPUT_LOCATION(0) out float4 ocol0;\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rename main, since we need to set up globals
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::D3D)
|
|
|
|
{
|
|
|
|
ss << R"(
|
|
|
|
#define main real_main
|
|
|
|
static float3 v_tex0;
|
|
|
|
static float4 ocol0;
|
|
|
|
|
|
|
|
// Wrappers for sampling functions.
|
|
|
|
#define texture(sampler, coords) sampler.Sample(sampler##_ss, coords)
|
|
|
|
#define textureOffset(sampler, coords, offset) sampler.Sample(sampler##_ss, coords, offset)
|
|
|
|
)";
|
|
|
|
}
|
|
|
|
|
|
|
|
ss << R"(
|
2019-10-02 01:52:52 +00:00
|
|
|
float4 Sample() { return texture(samp0, v_tex0); }
|
|
|
|
float4 SampleLocation(float2 location) { return texture(samp0, float3(location, float(v_tex0.z))); }
|
2019-02-15 01:59:50 +00:00
|
|
|
float4 SampleLayer(int layer) { return texture(samp0, float3(v_tex0.xy, float(layer))); }
|
2019-10-02 01:52:52 +00:00
|
|
|
#define SampleOffset(offset) textureOffset(samp0, v_tex0, offset)
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2019-05-02 02:47:50 +00:00
|
|
|
float2 GetWindowResolution()
|
|
|
|
{
|
|
|
|
return window_resolution.xy;
|
|
|
|
}
|
|
|
|
|
2021-04-13 17:14:17 +00:00
|
|
|
float2 GetInvWindowResolution()
|
|
|
|
{
|
|
|
|
return window_resolution.zw;
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
float2 GetResolution()
|
|
|
|
{
|
|
|
|
return resolution.xy;
|
|
|
|
}
|
|
|
|
|
|
|
|
float2 GetInvResolution()
|
|
|
|
{
|
|
|
|
return resolution.zw;
|
|
|
|
}
|
|
|
|
|
|
|
|
float2 GetCoordinates()
|
|
|
|
{
|
|
|
|
return v_tex0.xy;
|
|
|
|
}
|
|
|
|
|
2019-10-02 01:52:52 +00:00
|
|
|
float GetLayer()
|
|
|
|
{
|
|
|
|
return v_tex0.z;
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
uint GetTime()
|
|
|
|
{
|
|
|
|
return time;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetOutput(float4 color)
|
|
|
|
{
|
|
|
|
ocol0 = color;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define GetOption(x) (x)
|
|
|
|
#define OptionEnabled(x) ((x) != 0)
|
|
|
|
|
|
|
|
)";
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string PostProcessing::GetFooter() const
|
|
|
|
{
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::D3D)
|
|
|
|
{
|
|
|
|
return R"(
|
|
|
|
|
|
|
|
#undef main
|
|
|
|
void main(in float3 v_tex0_ : TEXCOORD0, out float4 ocol0_ : SV_Target)
|
|
|
|
{
|
|
|
|
v_tex0 = v_tex0_;
|
|
|
|
real_main();
|
|
|
|
ocol0_ = ocol0;
|
|
|
|
})";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PostProcessing::CompileVertexShader()
|
|
|
|
{
|
2019-09-14 20:40:34 +00:00
|
|
|
std::ostringstream ss;
|
2019-02-15 01:59:50 +00:00
|
|
|
ss << GetUniformBufferHeader();
|
|
|
|
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::D3D)
|
|
|
|
{
|
|
|
|
ss << "void main(in uint id : SV_VertexID, out float3 v_tex0 : TEXCOORD0,\n";
|
|
|
|
ss << " out float4 opos : SV_Position) {\n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-10-02 02:59:50 +00:00
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
|
|
|
|
{
|
|
|
|
ss << "VARYING_LOCATION(0) out VertexData {\n";
|
|
|
|
ss << " float3 v_tex0;\n";
|
|
|
|
ss << "};\n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ss << "VARYING_LOCATION(0) out float3 v_tex0;\n";
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
ss << "#define id gl_VertexID\n";
|
|
|
|
ss << "#define opos gl_Position\n";
|
|
|
|
ss << "void main() {\n";
|
|
|
|
}
|
|
|
|
ss << " v_tex0 = float3(float((id << 1) & 2), float(id & 2), 0.0f);\n";
|
|
|
|
ss << " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n";
|
2019-10-02 01:52:52 +00:00
|
|
|
ss << " v_tex0 = float3(src_rect.xy + (src_rect.zw * v_tex0.xy), float(src_layer));\n";
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan)
|
|
|
|
ss << " opos.y = -opos.y;\n";
|
|
|
|
|
|
|
|
ss << "}\n";
|
|
|
|
|
2021-12-09 22:00:08 +00:00
|
|
|
m_vertex_shader = g_renderer->CreateShaderFromSource(ShaderStage::Vertex, ss.str(),
|
|
|
|
"Post-processing vertex shader");
|
2019-02-15 01:59:50 +00:00
|
|
|
if (!m_vertex_shader)
|
|
|
|
{
|
2020-11-14 03:33:26 +00:00
|
|
|
PanicAlertFmt("Failed to compile post-processing vertex shader");
|
2019-02-15 01:59:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct BuiltinUniforms
|
|
|
|
{
|
|
|
|
float resolution[4];
|
2019-05-02 02:47:50 +00:00
|
|
|
float window_resolution[4];
|
2019-02-15 01:59:50 +00:00
|
|
|
float src_rect[4];
|
2019-10-02 01:52:52 +00:00
|
|
|
s32 src_layer;
|
|
|
|
u32 time;
|
2019-02-15 01:59:50 +00:00
|
|
|
u32 padding[2];
|
|
|
|
};
|
|
|
|
|
|
|
|
size_t PostProcessing::CalculateUniformsSize() const
|
|
|
|
{
|
|
|
|
// Allocate a vec4 for each uniform to simplify allocation.
|
|
|
|
return sizeof(BuiltinUniforms) + m_config.GetOptions().size() * sizeof(float) * 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
|
|
|
|
const AbstractTexture* src_tex, int src_layer)
|
|
|
|
{
|
2019-05-02 02:47:50 +00:00
|
|
|
const auto& window_rect = g_renderer->GetTargetRectangle();
|
2019-02-15 01:59:50 +00:00
|
|
|
const float rcp_src_width = 1.0f / src_tex->GetWidth();
|
|
|
|
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()),
|
|
|
|
rcp_src_width, rcp_src_height},
|
2019-05-02 02:47:50 +00:00
|
|
|
{static_cast<float>(window_rect.GetWidth()), static_cast<float>(window_rect.GetHeight()),
|
2021-04-13 17:14:17 +00:00
|
|
|
1.0f / static_cast<float>(window_rect.GetWidth()),
|
|
|
|
1.0f / static_cast<float>(window_rect.GetHeight())},
|
2019-02-15 01:59:50 +00:00
|
|
|
{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.GetHeight()) * rcp_src_height},
|
2019-10-02 01:52:52 +00:00
|
|
|
static_cast<s32>(src_layer),
|
|
|
|
static_cast<u32>(m_timer.GetTimeElapsed()),
|
2019-02-15 01:59:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
u8* buf = m_uniform_staging_buffer.data();
|
|
|
|
std::memcpy(buf, &builtin_uniforms, sizeof(builtin_uniforms));
|
|
|
|
buf += sizeof(builtin_uniforms);
|
|
|
|
|
|
|
|
for (const auto& it : m_config.GetOptions())
|
|
|
|
{
|
|
|
|
union
|
|
|
|
{
|
|
|
|
u32 as_bool[4];
|
|
|
|
s32 as_int[4];
|
|
|
|
float as_float[4];
|
|
|
|
} value = {};
|
|
|
|
|
|
|
|
switch (it.second.m_type)
|
|
|
|
{
|
|
|
|
case PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_BOOL:
|
|
|
|
value.as_bool[0] = it.second.m_bool_value ? 1 : 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER:
|
|
|
|
ASSERT(it.second.m_integer_values.size() < 4);
|
|
|
|
std::copy_n(it.second.m_integer_values.begin(), it.second.m_integer_values.size(),
|
|
|
|
value.as_int);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_FLOAT:
|
|
|
|
ASSERT(it.second.m_float_values.size() < 4);
|
|
|
|
std::copy_n(it.second.m_float_values.begin(), it.second.m_float_values.size(),
|
|
|
|
value.as_float);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::memcpy(buf, &value, sizeof(value));
|
|
|
|
buf += sizeof(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PostProcessing::CompilePixelShader()
|
|
|
|
{
|
|
|
|
m_pipeline.reset();
|
|
|
|
m_pixel_shader.reset();
|
|
|
|
|
|
|
|
// Generate GLSL and compile the new shader.
|
|
|
|
m_config.LoadShader(g_ActiveConfig.sPostProcessingShader);
|
|
|
|
m_pixel_shader = g_renderer->CreateShaderFromSource(
|
2021-12-09 22:00:08 +00:00
|
|
|
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(),
|
|
|
|
fmt::format("Post-processing pixel shader: {}", m_config.GetShader()));
|
2019-02-15 01:59:50 +00:00
|
|
|
if (!m_pixel_shader)
|
|
|
|
{
|
2020-11-14 03:33:26 +00:00
|
|
|
PanicAlertFmt("Failed to compile post-processing shader {}", m_config.GetShader());
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
// Use default shader.
|
|
|
|
m_config.LoadDefaultShader();
|
|
|
|
m_pixel_shader = g_renderer->CreateShaderFromSource(
|
2021-12-09 22:00:08 +00:00
|
|
|
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(),
|
|
|
|
"Default post-processing pixel shader");
|
2019-02-15 01:59:50 +00:00
|
|
|
if (!m_pixel_shader)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_uniform_staging_buffer.resize(CalculateUniformsSize());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PostProcessing::CompilePipeline()
|
|
|
|
{
|
|
|
|
AbstractPipelineConfig config = {};
|
|
|
|
config.vertex_shader = m_vertex_shader.get();
|
2019-10-02 01:33:20 +00:00
|
|
|
config.geometry_shader =
|
2019-10-02 02:19:47 +00:00
|
|
|
g_renderer->UseGeometryShaderForUI() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr;
|
2019-02-15 01:59:50 +00:00
|
|
|
config.pixel_shader = m_pixel_shader.get();
|
|
|
|
config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
|
|
|
|
config.depth_state = RenderState::GetNoDepthTestingDepthState();
|
|
|
|
config.blending_state = RenderState::GetNoBlendingBlendState();
|
|
|
|
config.framebuffer_state = RenderState::GetColorFramebufferState(m_framebuffer_format);
|
|
|
|
config.usage = AbstractPipelineUsage::Utility;
|
|
|
|
m_pipeline = g_renderer->CreatePipeline(config);
|
|
|
|
if (!m_pipeline)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} // namespace VideoCommon
|