diff --git a/Source/Core/Common/IniFile.cpp b/Source/Core/Common/IniFile.cpp index da9d99e250..f3ea5751ba 100644 --- a/Source/Core/Common/IniFile.cpp +++ b/Source/Core/Common/IniFile.cpp @@ -19,9 +19,7 @@ #include "Common/IniFile.h" #include "Common/StringUtil.h" -namespace { - -void ParseLine(const std::string& line, std::string* keyOut, std::string* valueOut) +void IniFile::ParseLine(const std::string& line, std::string* keyOut, std::string* valueOut) { if (line[0] == '#') return; @@ -40,8 +38,6 @@ void ParseLine(const std::string& line, std::string* keyOut, std::string* valueO } } -} - const std::string& IniFile::NULL_STRING = ""; void IniFile::Section::Set(const std::string& key, const std::string& newValue) diff --git a/Source/Core/Common/IniFile.h b/Source/Core/Common/IniFile.h index 3ff7e39794..1e62b5c7f7 100644 --- a/Source/Core/Common/IniFile.h +++ b/Source/Core/Common/IniFile.h @@ -116,6 +116,11 @@ public: Section* GetOrCreateSection(const std::string& section); + // This function is related to parsing data from lines of INI files + // It's used outside of IniFile, which is why it is exposed publicly + // In particular it is used in PostProcessing for its configuration + static void ParseLine(const std::string& line, std::string* keyOut, std::string* valueOut); + private: std::list
sections; diff --git a/Source/Core/DolphinWX/VideoConfigDiag.cpp b/Source/Core/DolphinWX/VideoConfigDiag.cpp index 565827fc22..13f3dad9b4 100644 --- a/Source/Core/DolphinWX/VideoConfigDiag.cpp +++ b/Source/Core/DolphinWX/VideoConfigDiag.cpp @@ -415,6 +415,11 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string &title, con else choice_ppshader->SetStringSelection(StrToWxStr(vconfig.sPostProcessingShader)); + // Should the configuration button be loaded by default? + PostProcessingShaderConfiguration postprocessing_shader; + postprocessing_shader.LoadShader(vconfig.sPostProcessingShader); + button_config_pp->Enable(postprocessing_shader.HasOptions()); + choice_ppshader->Bind(wxEVT_CHOICE, &VideoConfigDiag::Event_PPShader, this); szr_enh->Add(new wxStaticText(page_enh, -1, _("Post-Processing Effect:")), 1, wxALIGN_CENTER_VERTICAL, 0); diff --git a/Source/Core/DolphinWX/VideoConfigDiag.h b/Source/Core/DolphinWX/VideoConfigDiag.h index ab574cd2fe..6802f0be84 100644 --- a/Source/Core/DolphinWX/VideoConfigDiag.h +++ b/Source/Core/DolphinWX/VideoConfigDiag.h @@ -142,6 +142,19 @@ protected: else vconfig.sPostProcessingShader.clear(); + // Should we enable the configuration button? + PostProcessingShaderConfiguration postprocessing_shader; + postprocessing_shader.LoadShader(vconfig.sPostProcessingShader); + button_config_pp->Enable(postprocessing_shader.HasOptions()); + + ev.Skip(); + } + + void Event_ConfigurePPShader(wxCommandEvent &ev) + { + PostProcessingConfigDiag dialog(this, vconfig.sPostProcessingShader); + dialog.ShowModal(); + ev.Skip(); } diff --git a/Source/Core/VideoBackends/OGL/PostProcessing.cpp b/Source/Core/VideoBackends/OGL/PostProcessing.cpp index e885d63a56..06a15b07cc 100644 --- a/Source/Core/VideoBackends/OGL/PostProcessing.cpp +++ b/Source/Core/VideoBackends/OGL/PostProcessing.cpp @@ -40,14 +40,13 @@ static char s_vertex_shader[] = void Init() { - s_currentShader = ""; - s_enable = 0; - s_width = 0; - s_height = 0; + m_enable = false; + m_width = 0; + m_height = 0; - glGenFramebuffers(1, &s_fbo); - glGenTextures(1, &s_texture); - glBindTexture(GL_TEXTURE_2D, s_texture); + glGenFramebuffers(1, &m_fbo); + glGenTextures(1, &m_texture); + glBindTexture(GL_TEXTURE_2D, m_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); // disable mipmaps glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); @@ -59,7 +58,7 @@ void Init() void Shutdown() { - s_shader.Destroy(); + m_shader.Destroy(); glDeleteFramebuffers(1, &s_fbo); glDeleteTextures(1, &s_texture); @@ -84,6 +83,9 @@ void BlitToScreen() s_shader.Bind(); + glUniform4f(m_uniform_resolution, (float)m_width, (float)m_height, 1.0f/(float)m_width, 1.0f/(float)m_height); + glUniform1ui(m_uniform_time, (GLuint)m_timer.GetTimeElapsed()); + glUniform4f(s_uniform_resolution, (float)s_width, (float)s_height, 1.0f/(float)s_width, 1.0f/(float)s_height); glActiveTexture(GL_TEXTURE0+9); @@ -145,10 +147,82 @@ void ApplyShader() } // read uniform locations - s_uniform_resolution = glGetUniformLocation(s_shader.glprogid, "resolution"); + m_uniform_resolution = glGetUniformLocation(m_shader.glprogid, "resolution"); + m_uniform_time = glGetUniformLocation(m_shader.glprogid, "time"); + + for (const auto& it : m_options) + { + std::string glsl_name = "option_" + it.first; + m_uniform_bindings[it.first] = glGetUniformLocation(m_shader.glprogid, glsl_name.c_str()); + } // successful - s_enable = true; + m_enable = true; +} + +void OpenGLPostProcessing::CreateHeader() +{ + m_glsl_header = + // Required variables + // Shouldn't be accessed directly by the PP shader + // Texture sampler + "SAMPLER_BINDING(8) uniform sampler2D samp8;\n" + "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" + + // Output variable + "out float4 ocol0;\n" + // Input coordinates + "in float2 uv0;\n" + // Resolution + "uniform float4 resolution;\n" + // Time + "uniform uint time;\n" + + // Interfacing functions + "float4 Sample()\n" + "{\n" + "\treturn texture(samp9, uv0);\n" + "}\n" + + "float4 SampleLocation(float2 location)\n" + "{\n" + "\treturn texture(samp9, location);\n" + "}\n" + + "#define SampleOffset(offset) textureOffset(samp9, uv0, offset)\n" + + "float4 SampleFontLocation(float2 location)\n" + "{\n" + "\treturn texture(samp8, location);\n" + "}\n" + + "float2 GetResolution()\n" + "{\n" + "\treturn resolution.xy;\n" + "}\n" + + "float2 GetInvResolution()\n" + "{\n" + "\treturn resolution.zw;\n" + "}\n" + + "float2 GetCoordinates()\n" + "{\n" + "\treturn uv0;\n" + "}\n" + + "uint GetTime()\n" + "{\n" + "\treturn time;\n" + "}\n" + + "void SetOutput(float4 color)\n" + "{\n" + "\tocol0 = color;\n" + "}\n" + + "#define GetOption(x) (option_#x)\n" + "#define OptionEnabled(x) (option_#x != 0)\n"; } } // namespace diff --git a/Source/Core/VideoBackends/OGL/PostProcessing.h b/Source/Core/VideoBackends/OGL/PostProcessing.h index 1a6f015bbc..4f2fb87250 100644 --- a/Source/Core/VideoBackends/OGL/PostProcessing.h +++ b/Source/Core/VideoBackends/OGL/PostProcessing.h @@ -4,26 +4,38 @@ #pragma once +#include +#include + #include "VideoBackends/OGL/GLUtil.h" #include "VideoCommon/VideoCommon.h" namespace OGL { -namespace PostProcessing +class OpenGLPostProcessing : public PostProcessingShaderImplementation { +public: + OpenGLPostProcessing(); + ~OpenGLPostProcessing(); -void Init(); -void Shutdown(); + void BindTargetFramebuffer() override; + void BlitToScreen() override; + void Update(u32 width, u32 height) override; + void ApplyShader() override; -void BindTargetFramebuffer(); -void BlitToScreen(); -void Update(u32 width, u32 height); +private: + GLuint m_fbo; + GLuint m_texture; + SHADER m_shader; + GLuint m_uniform_resolution; + GLuint m_uniform_time; + std::string m_glsl_header; -void ReloadShader(); + std::unordered_map m_uniform_bindings; -void ApplyShader(); - -} // namespace + void CreateHeader(); + std::string LoadShaderOptions(const std::string& code); +}; } // namespace diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 844184210d..ef71208943 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -18,6 +18,7 @@ set(SRCS BPFunctions.cpp PixelEngine.cpp PixelShaderGen.cpp PixelShaderManager.cpp + PostProcessing.cpp RenderBase.cpp Statistics.cpp TextureCacheBase.cpp diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp new file mode 100644 index 0000000000..019001da15 --- /dev/null +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -0,0 +1,304 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include + +#include "Common/CommonPaths.h" +#include "Common/FileUtil.h" +#include "Common/IniFile.h" +#include "Common/StringUtil.h" + +#include "VideoCommon/PostProcessing.h" +#include "VideoCommon/VideoConfig.h" + + +PostProcessingShaderImplementation::PostProcessingShaderImplementation() +{ + m_timer.Start(); +} + +PostProcessingShaderImplementation::~PostProcessingShaderImplementation() +{ + m_timer.Stop(); +} + +std::string PostProcessingShaderConfiguration::LoadShader(std::string shader) +{ + // Load the shader from the configuration if there isn't one sent to us. + if (shader == "") + shader = g_ActiveConfig.sPostProcessingShader; + m_current_shader = shader; + + // loading shader code + std::string code; + std::string path = File::GetUserPath(D_SHADERS_IDX) + shader + ".glsl"; + + if (!File::Exists(path)) + { + // Fallback to shared user dir + path = File::GetSysDirectory() + SHADERS_DIR DIR_SEP + shader + ".glsl"; + } + + if (!File::ReadFileToString(path, code)) + { + ERROR_LOG(VIDEO, "Post-processing shader not found: %s", path.c_str()); + return ""; + } + + LoadOptions(code); + LoadOptionsConfiguration(); + + return code; +} + +void PostProcessingShaderConfiguration::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> m_options; + }; + + std::vector option_strings; + GLSLStringOption* current_strings = nullptr; + while (!in.eof()) + { + std::string line; + + if (std::getline(in, line)) + { +#ifndef _WIN32 + // Check for CRLF eol and convert it to LF + if (!line.empty() && line.at(line.size()-1) == '\r') + { + line.erase(line.size()-1); + } +#endif + + if (line.size() > 0) + { + if (line[0] == '[') + { + size_t endpos = line.find("]"); + + if (endpos != std::string::npos) + { + // New section! + std::string sub = line.substr(1, endpos - 1); + option_strings.push_back({ sub }); + current_strings = &option_strings.back(); + } + } + else + { + if (current_strings) + { + std::string key, value; + IniFile::ParseLine(line, &key, &value); + + if (!(key == "" && value == "")) + current_strings->m_options.push_back(std::make_pair(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* output_integer = nullptr; + std::vector* 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 PostProcessingShaderConfiguration::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 != "") + 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 != "") + TryParseVector(value, &it.second.m_float_values); + } + break; + } + } +} + +void PostProcessingShaderConfiguration::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 += StringFromFormat("%d%s", 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::string value = ""; + for (size_t i = 0; i < it.second.m_float_values.size(); ++i) + value += StringFromFormat("%f%s", it.second.m_float_values[i], i == (it.second.m_float_values.size() - 1) ? "": ", "); + ini.GetOrCreateSection(section)->Set(it.second.m_option_name, value); + } + break; + } + } + ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX)); +} + +void PostProcessingShaderConfiguration::ReloadShader() +{ + m_current_shader = ""; +} + +void PostProcessingShaderConfiguration::SetOptionf(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 PostProcessingShaderConfiguration::SetOptioni(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 PostProcessingShaderConfiguration::SetOptionb(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; +} diff --git a/Source/Core/VideoCommon/PostProcessing.h b/Source/Core/VideoCommon/PostProcessing.h new file mode 100644 index 0000000000..0f539dbb05 --- /dev/null +++ b/Source/Core/VideoCommon/PostProcessing.h @@ -0,0 +1,104 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/StringUtil.h" +#include "Common/Timer.h" + +class PostProcessingShaderConfiguration +{ +public: + struct ConfigurationOption + { + enum OptionType + { + OPTION_BOOL = 0, + OPTION_FLOAT, + OPTION_INTEGER, + }; + + bool m_bool_value; + + std::vector m_float_values; + std::vector m_integer_values; + + std::vector m_float_min_values; + std::vector m_integer_min_values; + + std::vector m_float_max_values; + std::vector m_integer_max_values; + + std::vector m_float_step_values; + std::vector m_integer_step_values; + + OptionType m_type; + + std::string m_gui_name; + std::string m_option_name; + std::string m_dependent_option; + bool m_dirty; + }; + + typedef std::map ConfigMap; + + PostProcessingShaderConfiguration() : m_current_shader("") {} + virtual ~PostProcessingShaderConfiguration() {} + + // Loads the configuration with a shader + // If the argument is "" the class will load the shader from the g_activeConfig option. + // Returns the loaded shader source from file + std::string LoadShader(std::string shader = ""); + void SaveOptionsConfiguration(); + void ReloadShader(); + std::string GetShader() { return m_current_shader; } + + bool IsDirty() { return m_any_options_dirty; } + void SetDirty(bool dirty) { m_any_options_dirty = dirty; } + + bool HasOptions() { return m_options.size() > 0; } + ConfigMap& GetOptions() { return m_options; } + const ConfigurationOption& GetOption(const std::string& option) { return m_options[option]; } + + // For updating option's values + void SetOptionf(std::string option, int index, float value); + void SetOptioni(std::string option, int index, s32 value); + void SetOptionb(std::string option, bool value); + +private: + bool m_any_options_dirty; + std::string m_current_shader; + ConfigMap m_options; + + void LoadOptions(const std::string& code); + void LoadOptionsConfiguration(); +}; + +class PostProcessingShaderImplementation +{ +public: + PostProcessingShaderImplementation(); + virtual ~PostProcessingShaderImplementation(); + + PostProcessingShaderConfiguration* GetConfig() { return &m_config; } + + // Should be implemented by the backends for backend specific code + virtual void BindTargetFramebuffer() = 0; + virtual void BlitToScreen() = 0; + virtual void Update(u32 width, u32 height) = 0; + virtual void ApplyShader() = 0; + +protected: + bool m_enable; + u32 m_width; + u32 m_height; + // Timer for determining our time value + Common::Timer m_timer; + + PostProcessingShaderConfiguration m_config; +}; diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 4ba52d09fe..07b4f8bccb 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -59,6 +59,8 @@ int Renderer::s_target_height; int Renderer::s_backbuffer_width; int Renderer::s_backbuffer_height; +PostProcessingShaderImplementation* Renderer::m_post_processor; + TargetRectangle Renderer::target_rc; int Renderer::s_LastEFBScale; diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 8707219d82..3ed767b348 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -115,6 +115,8 @@ public: static PEControl::PixelFormat GetPrevPixelFormat() { return prev_efb_format; } static void StorePixelFormat(PEControl::PixelFormat new_format) { prev_efb_format = new_format; } + PostProcessingShaderImplementation* GetPostProcessor() { return m_post_processor; } + protected: static void CalculateTargetScale(int x, int y, int &scaledX, int &scaledY); @@ -153,6 +155,8 @@ protected: FPSCounter m_fps_counter; + static PostProcessingShaderImplementation* m_post_processor; + private: static PEControl::PixelFormat prev_efb_format; static unsigned int efb_scale_numeratorX; diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj b/Source/Core/VideoCommon/VideoCommon.vcxproj index a7e8d7d23b..f00e031cc3 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj @@ -56,6 +56,7 @@ + @@ -105,6 +106,7 @@ + @@ -148,4 +150,4 @@ - \ No newline at end of file + diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters index cf8611b719..85f6424c24 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters @@ -107,6 +107,9 @@ Util + + Util + Util @@ -234,6 +237,9 @@ Util + + Util + Util @@ -266,4 +272,4 @@ - \ No newline at end of file +