mirror of
https://github.com/libretro/dolphin
synced 2024-11-09 06:53:01 -05:00
86f8768268
These are only ever used with ShaderCode instances and nothing else. Given that, we can convert these helper functions to expect that type of object as an argument and remove the need for templates, improving compiler throughput a marginal amount, as the template instantiation process doesn't need to be performed. We can also move the definitions of these functions into the cpp file, which allows us to remove a few inclusions from the ShaderGenCommon header. This uncovered a few instances of indirect inclusions being relied upon in other source files. One other benefit is this allows changes to be made to the definitions of the functions without needing to recompile all translation units that make use of these functions, making change testing a little quicker. Moving the definitions into the cpp file also allows us to completely hide DefineOutputMember() from external view, given it's only ever used inside of GenerateVSOutputMembers().
765 lines
21 KiB
C++
765 lines
21 KiB
C++
// Copyright 2014 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "VideoCommon/PostProcessing.h"
|
|
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include "Common/Assert.h"
|
|
#include "Common/CommonPaths.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/FileSearch.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Common/IniFile.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/MsgHandler.h"
|
|
#include "Common/StringUtil.h"
|
|
|
|
#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"
|
|
#include "VideoCommon/VideoCommon.h"
|
|
#include "VideoCommon/VideoConfig.h"
|
|
|
|
namespace VideoCommon
|
|
{
|
|
static const char s_default_shader[] = "void main() { SetOutput(Sample()); }\n";
|
|
|
|
PostProcessingConfiguration::PostProcessingConfiguration() = default;
|
|
|
|
PostProcessingConfiguration::~PostProcessingConfiguration() = default;
|
|
|
|
void PostProcessingConfiguration::LoadShader(const std::string& shader)
|
|
{
|
|
// Load the shader from the configuration if there isn't one sent to us.
|
|
m_current_shader = shader;
|
|
if (shader.empty())
|
|
{
|
|
LoadDefaultShader();
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// loading shader code
|
|
std::string 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, code))
|
|
{
|
|
ERROR_LOG(VIDEO, "Post-processing shader not found: %s", path.c_str());
|
|
LoadDefaultShader();
|
|
return;
|
|
}
|
|
|
|
LoadOptions(code);
|
|
LoadOptionsConfiguration();
|
|
m_current_shader_code = code;
|
|
}
|
|
|
|
void PostProcessingConfiguration::LoadDefaultShader()
|
|
{
|
|
m_options.clear();
|
|
m_any_options_dirty = false;
|
|
m_current_shader_code = s_default_shader;
|
|
}
|
|
|
|
void PostProcessingConfiguration::LoadOptions(const std::string& code)
|
|
{
|
|
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);
|
|
|
|
m_options.clear();
|
|
m_any_options_dirty = true;
|
|
|
|
if (configuration_start == std::string::npos || configuration_end == std::string::npos)
|
|
{
|
|
// Issue loading configuration or there isn't one.
|
|
return;
|
|
}
|
|
|
|
std::string configuration_string =
|
|
code.substr(configuration_start + config_start_delimiter.size(),
|
|
configuration_end - configuration_start - config_start_delimiter.size());
|
|
|
|
std::istringstream in(configuration_string);
|
|
|
|
struct GLSLStringOption
|
|
{
|
|
std::string m_type;
|
|
std::vector<std::pair<std::string, std::string>> m_options;
|
|
};
|
|
|
|
std::vector<GLSLStringOption> option_strings;
|
|
GLSLStringOption* current_strings = nullptr;
|
|
while (!in.eof())
|
|
{
|
|
std::string line_str;
|
|
if (std::getline(in, line_str))
|
|
{
|
|
std::string_view line = line_str;
|
|
|
|
#ifndef _WIN32
|
|
// Check for CRLF eol and convert it to LF
|
|
if (!line.empty() && line.at(line.size() - 1) == '\r')
|
|
line.remove_suffix(1);
|
|
#endif
|
|
|
|
if (!line.empty())
|
|
{
|
|
if (line[0] == '[')
|
|
{
|
|
size_t endpos = line.find("]");
|
|
|
|
if (endpos != std::string::npos)
|
|
{
|
|
// New section!
|
|
std::string_view sub = line.substr(1, endpos - 1);
|
|
option_strings.push_back({std::string(sub)});
|
|
current_strings = &option_strings.back();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (current_strings)
|
|
{
|
|
std::string key, value;
|
|
IniFile::ParseLine(line, &key, &value);
|
|
|
|
if (!(key.empty() && value.empty()))
|
|
current_strings->m_options.emplace_back(key, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& it : option_strings)
|
|
{
|
|
ConfigurationOption option;
|
|
option.m_dirty = true;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void PostProcessingConfiguration::LoadOptionsConfiguration()
|
|
{
|
|
IniFile ini;
|
|
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
|
|
std::string section = m_current_shader + "-options";
|
|
|
|
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);
|
|
if (!value.empty())
|
|
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);
|
|
if (!value.empty())
|
|
TryParseVector(value, &it.second.m_float_values);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PostProcessingConfiguration::SaveOptionsConfiguration()
|
|
{
|
|
IniFile ini;
|
|
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
|
|
std::string section = m_current_shader + "-options";
|
|
|
|
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:
|
|
{
|
|
std::string value;
|
|
for (size_t i = 0; i < it.second.m_integer_values.size(); ++i)
|
|
{
|
|
value += fmt::format("{}{}", it.second.m_integer_values[i],
|
|
i == (it.second.m_integer_values.size() - 1) ? "" : ", ");
|
|
}
|
|
ini.GetOrCreateSection(section)->Set(it.second.m_option_name, value);
|
|
}
|
|
break;
|
|
case ConfigurationOption::OptionType::OPTION_FLOAT:
|
|
{
|
|
std::ostringstream value;
|
|
value.imbue(std::locale("C"));
|
|
|
|
for (size_t i = 0; i < it.second.m_float_values.size(); ++i)
|
|
{
|
|
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());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX));
|
|
}
|
|
|
|
void PostProcessingConfiguration::SetOptionf(const std::string& option, int index, float value)
|
|
{
|
|
auto it = m_options.find(option);
|
|
|
|
it->second.m_float_values[index] = value;
|
|
it->second.m_dirty = true;
|
|
m_any_options_dirty = true;
|
|
}
|
|
|
|
void PostProcessingConfiguration::SetOptioni(const std::string& option, int index, s32 value)
|
|
{
|
|
auto it = m_options.find(option);
|
|
|
|
it->second.m_integer_values[index] = value;
|
|
it->second.m_dirty = true;
|
|
m_any_options_dirty = true;
|
|
}
|
|
|
|
void PostProcessingConfiguration::SetOptionb(const std::string& option, bool value)
|
|
{
|
|
auto it = m_options.find(option);
|
|
|
|
it->second.m_bool_value = value;
|
|
it->second.m_dirty = true;
|
|
m_any_options_dirty = true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
std::vector<std::string> PostProcessing::GetPassiveShaderList()
|
|
{
|
|
return GetShaders(PASSIVE_DIR DIR_SEP);
|
|
}
|
|
|
|
bool PostProcessing::Initialize(AbstractTextureFormat format)
|
|
{
|
|
m_framebuffer_format = format;
|
|
if (!CompileVertexShader() || !CompilePixelShader() || !CompilePipeline())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void PostProcessing::RecompileShader()
|
|
{
|
|
m_pipeline.reset();
|
|
m_pixel_shader.reset();
|
|
if (!CompilePixelShader())
|
|
return;
|
|
|
|
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
|
|
{
|
|
std::ostringstream ss;
|
|
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";
|
|
ss << " float4 window_resolution;\n";
|
|
ss << " float4 src_rect;\n";
|
|
ss << " int src_layer;\n";
|
|
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)
|
|
{
|
|
ss << fmt::format(" int {};\n", it.first);
|
|
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)
|
|
ss << fmt::format(" int {};\n", it.first);
|
|
else
|
|
ss << fmt::format(" int{} {};\n", count, it.first);
|
|
|
|
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)
|
|
ss << fmt::format(" float {};\n", it.first);
|
|
else
|
|
ss << fmt::format(" float{} {};\n", count, it.first);
|
|
|
|
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
|
|
{
|
|
std::ostringstream ss;
|
|
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";
|
|
|
|
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";
|
|
}
|
|
|
|
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"(
|
|
float4 Sample() { return texture(samp0, v_tex0); }
|
|
float4 SampleLocation(float2 location) { return texture(samp0, float3(location, float(v_tex0.z))); }
|
|
float4 SampleLayer(int layer) { return texture(samp0, float3(v_tex0.xy, float(layer))); }
|
|
#define SampleOffset(offset) textureOffset(samp0, v_tex0, offset)
|
|
|
|
float2 GetWindowResolution()
|
|
{
|
|
return window_resolution.xy;
|
|
}
|
|
|
|
float2 GetResolution()
|
|
{
|
|
return resolution.xy;
|
|
}
|
|
|
|
float2 GetInvResolution()
|
|
{
|
|
return resolution.zw;
|
|
}
|
|
|
|
float2 GetCoordinates()
|
|
{
|
|
return v_tex0.xy;
|
|
}
|
|
|
|
float GetLayer()
|
|
{
|
|
return v_tex0.z;
|
|
}
|
|
|
|
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()
|
|
{
|
|
std::ostringstream ss;
|
|
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
|
|
{
|
|
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";
|
|
}
|
|
|
|
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";
|
|
ss << " v_tex0 = float3(src_rect.xy + (src_rect.zw * v_tex0.xy), float(src_layer));\n";
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan)
|
|
ss << " opos.y = -opos.y;\n";
|
|
|
|
ss << "}\n";
|
|
|
|
m_vertex_shader = g_renderer->CreateShaderFromSource(ShaderStage::Vertex, ss.str());
|
|
if (!m_vertex_shader)
|
|
{
|
|
PanicAlert("Failed to compile post-processing vertex shader");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct BuiltinUniforms
|
|
{
|
|
float resolution[4];
|
|
float window_resolution[4];
|
|
float src_rect[4];
|
|
s32 src_layer;
|
|
u32 time;
|
|
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)
|
|
{
|
|
const auto& window_rect = g_renderer->GetTargetRectangle();
|
|
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},
|
|
{static_cast<float>(window_rect.GetWidth()), static_cast<float>(window_rect.GetHeight()),
|
|
0.0f, 0.0f},
|
|
{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},
|
|
static_cast<s32>(src_layer),
|
|
static_cast<u32>(m_timer.GetTimeElapsed()),
|
|
};
|
|
|
|
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(
|
|
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter());
|
|
if (!m_pixel_shader)
|
|
{
|
|
PanicAlert("Failed to compile post-processing shader %s", m_config.GetShader().c_str());
|
|
|
|
// Use default shader.
|
|
m_config.LoadDefaultShader();
|
|
m_pixel_shader = g_renderer->CreateShaderFromSource(
|
|
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter());
|
|
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();
|
|
config.geometry_shader =
|
|
g_renderer->UseGeometryShaderForUI() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr;
|
|
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
|