duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

postprocessing.cpp (26132B)


      1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "postprocessing.h"
      5 #include "gpu_device.h"
      6 #include "host.h"
      7 #include "imgui_manager.h"
      8 #include "postprocessing_shader.h"
      9 #include "postprocessing_shader_fx.h"
     10 #include "postprocessing_shader_glsl.h"
     11 
     12 // TODO: Remove me
     13 #include "core/host.h"
     14 #include "core/host_interface_progress_callback.h"
     15 #include "core/settings.h"
     16 
     17 #include "IconsFontAwesome5.h"
     18 #include "common/assert.h"
     19 #include "common/error.h"
     20 #include "common/file_system.h"
     21 #include "common/log.h"
     22 #include "common/path.h"
     23 #include "common/progress_callback.h"
     24 #include "common/small_string.h"
     25 #include "common/string_util.h"
     26 #include "common/timer.h"
     27 #include "fmt/format.h"
     28 
     29 Log_SetChannel(PostProcessing);
     30 
     31 // TODO: ProgressCallbacks for shader compiling, it can be a bit slow.
     32 // TODO: buffer width/height is wrong on resize, need to change it somehow.
     33 
     34 namespace PostProcessing {
     35 template<typename T>
     36 static u32 ParseVector(std::string_view line, ShaderOption::ValueVector* values);
     37 
     38 static TinyString ValueToString(ShaderOption::Type type, u32 vector_size, const ShaderOption::ValueVector& value);
     39 
     40 static TinyString GetStageConfigSection(const char* section, u32 index);
     41 static void CopyStageConfig(SettingsInterface& si, const char* section, u32 old_index, u32 new_index);
     42 static void SwapStageConfig(SettingsInterface& si, const char* section, u32 lhs_index, u32 rhs_index);
     43 static std::unique_ptr<Shader> TryLoadingShader(const std::string& shader_name, bool only_config, Error* error);
     44 static SettingsInterface& GetLoadSettingsInterface(const char* section);
     45 
     46 template<typename T>
     47 ALWAYS_INLINE void ForAllChains(const T& F)
     48 {
     49   F(DisplayChain);
     50   F(InternalChain);
     51 }
     52 
     53 Chain DisplayChain(Config::DISPLAY_CHAIN_SECTION);
     54 Chain InternalChain(Config::INTERNAL_CHAIN_SECTION);
     55 
     56 static Common::Timer s_timer;
     57 
     58 static std::unordered_map<u64, std::unique_ptr<GPUSampler>> s_samplers;
     59 static std::unique_ptr<GPUTexture> s_dummy_texture;
     60 } // namespace PostProcessing
     61 
     62 template<typename T>
     63 u32 PostProcessing::ParseVector(std::string_view line, ShaderOption::ValueVector* values)
     64 {
     65   u32 index = 0;
     66   size_t start = 0;
     67   while (index < PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS)
     68   {
     69     while (start < line.size() && std::isspace(line[start]))
     70       start++;
     71 
     72     if (start >= line.size())
     73       break;
     74 
     75     size_t end = line.find(',', start);
     76     if (end == std::string_view::npos)
     77       end = line.size();
     78 
     79     const std::string_view component = line.substr(start, end - start);
     80     T value = StringUtil::FromChars<T>(component).value_or(static_cast<T>(0));
     81     if constexpr (std::is_same_v<T, float>)
     82       (*values)[index++].float_value = value;
     83     else if constexpr (std::is_same_v<T, s32>)
     84       (*values)[index++].int_value = value;
     85 
     86     start = end + 1;
     87   }
     88 
     89   const u32 size = index;
     90 
     91   for (; index < PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS; index++)
     92   {
     93     if constexpr (std::is_same_v<T, float>)
     94       (*values)[index++].float_value = 0.0f;
     95     else if constexpr (std::is_same_v<T, s32>)
     96       (*values)[index++].int_value = 0;
     97   }
     98 
     99   return size;
    100 }
    101 
    102 u32 PostProcessing::ShaderOption::ParseFloatVector(std::string_view line, ValueVector* values)
    103 {
    104   return ParseVector<float>(line, values);
    105 }
    106 
    107 u32 PostProcessing::ShaderOption::ParseIntVector(std::string_view line, ValueVector* values)
    108 {
    109   return ParseVector<s32>(line, values);
    110 }
    111 
    112 TinyString PostProcessing::ValueToString(ShaderOption::Type type, u32 vector_size,
    113                                          const ShaderOption::ValueVector& value)
    114 {
    115   TinyString ret;
    116 
    117   for (u32 i = 0; i < vector_size; i++)
    118   {
    119     if (i > 0)
    120       ret.append(',');
    121 
    122     switch (type)
    123     {
    124       case ShaderOption::Type::Bool:
    125         ret.append((value[i].int_value != 0) ? "true" : "false");
    126         break;
    127 
    128       case ShaderOption::Type::Int:
    129         ret.append_format("{}", value[i].int_value);
    130         break;
    131 
    132       case ShaderOption::Type::Float:
    133         ret.append_format("{}", value[i].float_value);
    134         break;
    135 
    136       default:
    137         break;
    138     }
    139   }
    140 
    141   return ret;
    142 }
    143 
    144 std::vector<std::pair<std::string, std::string>> PostProcessing::GetAvailableShaderNames()
    145 {
    146   std::vector<std::pair<std::string, std::string>> names;
    147 
    148   FileSystem::FindResultsArray results;
    149   FileSystem::FindFiles(Path::Combine(EmuFolders::Resources, "shaders").c_str(), "*.glsl",
    150                         FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS, &results);
    151   FileSystem::FindFiles(EmuFolders::Shaders.c_str(), "*.glsl",
    152                         FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS |
    153                           FILESYSTEM_FIND_KEEP_ARRAY,
    154                         &results);
    155   std::sort(results.begin(), results.end(),
    156             [](const auto& lhs, const auto& rhs) { return lhs.FileName < rhs.FileName; });
    157 
    158   for (FILESYSTEM_FIND_DATA& fd : results)
    159   {
    160     size_t pos = fd.FileName.rfind('.');
    161     if (pos != std::string::npos && pos > 0)
    162       fd.FileName.erase(pos);
    163 
    164 #ifdef _WIN32
    165     // swap any backslashes for forward slashes so the config is cross-platform
    166     StringUtil::ReplaceAll(&fd.FileName, '\\', '/');
    167 #endif
    168 
    169     if (std::none_of(names.begin(), names.end(), [&fd](const auto& other) { return fd.FileName == other.second; }))
    170     {
    171       std::string display_name = fmt::format(TRANSLATE_FS("PostProcessing", "{} [GLSL]"), fd.FileName);
    172       names.emplace_back(std::move(display_name), std::move(fd.FileName));
    173     }
    174   }
    175 
    176   FileSystem::FindFiles(Path::Combine(EmuFolders::Shaders, "reshade" FS_OSPATH_SEPARATOR_STR "Shaders").c_str(), "*.fx",
    177                         FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS, &results);
    178   FileSystem::FindFiles(
    179     Path::Combine(EmuFolders::Resources, "shaders" FS_OSPATH_SEPARATOR_STR "reshade" FS_OSPATH_SEPARATOR_STR "Shaders")
    180       .c_str(),
    181     "*.fx",
    182     FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS | FILESYSTEM_FIND_KEEP_ARRAY,
    183     &results);
    184   std::sort(results.begin(), results.end(),
    185             [](const auto& lhs, const auto& rhs) { return lhs.FileName < rhs.FileName; });
    186 
    187   for (FILESYSTEM_FIND_DATA& fd : results)
    188   {
    189     size_t pos = fd.FileName.rfind('.');
    190     if (pos != std::string::npos && pos > 0)
    191       fd.FileName.erase(pos);
    192 
    193 #ifdef _WIN32
    194     // swap any backslashes for forward slashes so the config is cross-platform
    195     StringUtil::ReplaceAll(&fd.FileName, '\\', '/');
    196 #endif
    197 
    198     if (std::none_of(names.begin(), names.end(), [&fd](const auto& other) { return fd.FileName == other.second; }))
    199     {
    200       std::string display_name = fmt::format(TRANSLATE_FS("PostProcessing", "{} [ReShade]"), fd.FileName);
    201       names.emplace_back(std::move(display_name), std::move(fd.FileName));
    202     }
    203   }
    204 
    205   std::sort(names.begin(), names.end(),
    206             [](const std::pair<std::string, std::string>& lhs, const std::pair<std::string, std::string>& rhs) {
    207               return (StringUtil::Strcasecmp(lhs.first.c_str(), rhs.first.c_str()) < 0);
    208             });
    209 
    210   return names;
    211 }
    212 
    213 TinyString PostProcessing::GetStageConfigSection(const char* section, u32 index)
    214 {
    215   return TinyString::from_format("{}/Stage{}", section, index + 1);
    216 }
    217 
    218 void PostProcessing::CopyStageConfig(SettingsInterface& si, const char* section, u32 old_index, u32 new_index)
    219 {
    220   const TinyString old_section = GetStageConfigSection(section, old_index);
    221   const TinyString new_section = GetStageConfigSection(section, new_index);
    222 
    223   si.ClearSection(new_section);
    224 
    225   for (const auto& [key, value] : si.GetKeyValueList(old_section))
    226     si.SetStringValue(new_section, key.c_str(), value.c_str());
    227 }
    228 
    229 void PostProcessing::SwapStageConfig(SettingsInterface& si, const char* section, u32 lhs_index, u32 rhs_index)
    230 {
    231   const TinyString lhs_section = GetStageConfigSection(section, lhs_index);
    232   const TinyString rhs_section = GetStageConfigSection(section, rhs_index);
    233 
    234   const std::vector<std::pair<std::string, std::string>> lhs_kvs = si.GetKeyValueList(lhs_section);
    235   si.ClearSection(lhs_section);
    236 
    237   const std::vector<std::pair<std::string, std::string>> rhs_kvs = si.GetKeyValueList(rhs_section);
    238   si.ClearSection(rhs_section);
    239 
    240   for (const auto& [key, value] : rhs_kvs)
    241     si.SetStringValue(lhs_section, key.c_str(), value.c_str());
    242 
    243   for (const auto& [key, value] : lhs_kvs)
    244     si.SetStringValue(rhs_section, key.c_str(), value.c_str());
    245 }
    246 
    247 u32 PostProcessing::Config::GetStageCount(const SettingsInterface& si, const char* section)
    248 {
    249   return si.GetUIntValue(section, "StageCount", 0u);
    250 }
    251 
    252 std::string PostProcessing::Config::GetStageShaderName(const SettingsInterface& si, const char* section, u32 index)
    253 {
    254   return si.GetStringValue(GetStageConfigSection(section, index), "ShaderName");
    255 }
    256 
    257 std::vector<PostProcessing::ShaderOption> PostProcessing::Config::GetStageOptions(const SettingsInterface& si,
    258                                                                                   const char* section, u32 index)
    259 {
    260   std::vector<PostProcessing::ShaderOption> ret;
    261 
    262   const TinyString stage_section = GetStageConfigSection(section, index);
    263   const std::string shader_name = si.GetStringValue(stage_section, "ShaderName");
    264   if (shader_name.empty())
    265     return ret;
    266 
    267   std::unique_ptr<Shader> shader = TryLoadingShader(shader_name, true, nullptr);
    268   if (!shader)
    269     return ret;
    270 
    271   shader->LoadOptions(si, stage_section);
    272   ret = shader->TakeOptions();
    273   return ret;
    274 }
    275 
    276 std::vector<PostProcessing::ShaderOption> PostProcessing::Config::GetShaderOptions(const std::string& shader_name,
    277                                                                                    Error* error)
    278 {
    279   std::vector<PostProcessing::ShaderOption> ret;
    280   std::unique_ptr<Shader> shader = TryLoadingShader(shader_name, true, error);
    281   if (!shader)
    282     return ret;
    283 
    284   ret = shader->TakeOptions();
    285   return ret;
    286 }
    287 
    288 bool PostProcessing::Config::AddStage(SettingsInterface& si, const char* section, const std::string& shader_name,
    289                                       Error* error)
    290 {
    291   std::unique_ptr<Shader> shader = TryLoadingShader(shader_name, true, error);
    292   if (!shader)
    293     return false;
    294 
    295   const u32 index = GetStageCount(si, section);
    296   si.SetUIntValue(section, "StageCount", index + 1);
    297 
    298   const TinyString stage_section = GetStageConfigSection(section, index);
    299   si.SetStringValue(stage_section, "ShaderName", shader->GetName().c_str());
    300 
    301 #if 0
    302   // Leave options unset for now.
    303   for (const ShaderOption& option : shader->GetOptions())
    304   {
    305     si.SetStringValue(section, option.name.c_str(),
    306                       ValueToString(option.type, option.vector_size, option.default_value));
    307   }
    308 #endif
    309 
    310   return true;
    311 }
    312 
    313 void PostProcessing::Config::RemoveStage(SettingsInterface& si, const char* section, u32 index)
    314 {
    315   const u32 stage_count = GetStageCount(si, section);
    316   if (index >= stage_count)
    317     return;
    318 
    319   for (u32 i = index; i < (stage_count - 1); i++)
    320     CopyStageConfig(si, section, i + 1, i);
    321 
    322   si.ClearSection(GetStageConfigSection(section, stage_count - 1));
    323   si.SetUIntValue(section, "StageCount", stage_count - 1);
    324 }
    325 
    326 void PostProcessing::Config::MoveStageUp(SettingsInterface& si, const char* section, u32 index)
    327 {
    328   const u32 stage_count = GetStageCount(si, section);
    329   if (index == 0 || index >= stage_count)
    330     return;
    331 
    332   SwapStageConfig(si, section, index, index - 1);
    333 }
    334 
    335 void PostProcessing::Config::MoveStageDown(SettingsInterface& si, const char* section, u32 index)
    336 {
    337   const u32 stage_count = GetStageCount(si, section);
    338   if ((index + 1) >= stage_count)
    339     return;
    340 
    341   SwapStageConfig(si, section, index, index + 1);
    342 }
    343 
    344 void PostProcessing::Config::SetStageOption(SettingsInterface& si, const char* section, u32 index,
    345                                             const ShaderOption& option)
    346 {
    347   const TinyString stage_section = GetStageConfigSection(section, index);
    348   si.SetStringValue(stage_section, option.name.c_str(), ValueToString(option.type, option.vector_size, option.value));
    349 }
    350 
    351 void PostProcessing::Config::UnsetStageOption(SettingsInterface& si, const char* section, u32 index,
    352                                               const ShaderOption& option)
    353 {
    354   const TinyString stage_section = GetStageConfigSection(section, index);
    355   si.DeleteValue(stage_section, option.name.c_str());
    356 }
    357 
    358 void PostProcessing::Config::ClearStages(SettingsInterface& si, const char* section)
    359 {
    360   const u32 count = GetStageCount(si, section);
    361   for (s32 i = static_cast<s32>(count - 1); i >= 0; i--)
    362     si.ClearSection(GetStageConfigSection(section, static_cast<u32>(i)));
    363   si.SetUIntValue(section, "StageCount", 0);
    364 }
    365 
    366 PostProcessing::Chain::Chain(const char* section) : m_section(section)
    367 {
    368 }
    369 
    370 PostProcessing::Chain::~Chain() = default;
    371 
    372 bool PostProcessing::Chain::IsActive() const
    373 {
    374   return m_enabled && !m_stages.empty();
    375 }
    376 
    377 bool PostProcessing::Chain::IsInternalChain() const
    378 {
    379   return (this == &InternalChain);
    380 }
    381 
    382 void PostProcessing::Chain::ClearStagesWithError(const Error& error)
    383 {
    384   std::string msg = error.GetDescription();
    385   Host::AddIconOSDMessage(
    386     "PostProcessLoadFail", ICON_FA_EXCLAMATION_TRIANGLE,
    387     fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load post-processing chain: {}"),
    388                 msg.empty() ? TRANSLATE_SV("PostProcessing", "Unknown Error") : std::string_view(msg)),
    389     Host::OSD_ERROR_DURATION);
    390   m_stages.clear();
    391 }
    392 
    393 void PostProcessing::Chain::LoadStages()
    394 {
    395   auto lock = Host::GetSettingsLock();
    396   SettingsInterface& si = GetLoadSettingsInterface(m_section);
    397 
    398   m_enabled = si.GetBoolValue(m_section, "Enabled", false);
    399   m_wants_depth_buffer = false;
    400 
    401   const u32 stage_count = Config::GetStageCount(si, m_section);
    402   if (stage_count == 0)
    403     return;
    404 
    405   Error error;
    406   HostInterfaceProgressCallback progress;
    407   progress.SetProgressRange(stage_count);
    408 
    409   for (u32 i = 0; i < stage_count; i++)
    410   {
    411     std::string stage_name = Config::GetStageShaderName(si, m_section, i);
    412     if (stage_name.empty())
    413     {
    414       error.SetString(fmt::format("No stage name in stage {}.", i + 1));
    415       ClearStagesWithError(error);
    416       return;
    417     }
    418 
    419     lock.unlock();
    420     progress.FormatStatusText("Loading shader {}...", stage_name);
    421 
    422     std::unique_ptr<Shader> shader = TryLoadingShader(stage_name, false, &error);
    423     if (!shader)
    424     {
    425       ClearStagesWithError(error);
    426       return;
    427     }
    428 
    429     lock.lock();
    430     shader->LoadOptions(si, GetStageConfigSection(m_section, i));
    431     m_stages.push_back(std::move(shader));
    432 
    433     progress.IncrementProgressValue();
    434   }
    435 
    436   if (stage_count > 0)
    437     DEV_LOG("Loaded {} post-processing stages.", stage_count);
    438 
    439   // precompile shaders
    440   if (!IsInternalChain() && g_gpu_device && g_gpu_device->GetWindowFormat() != GPUTexture::Format::Unknown)
    441   {
    442     CheckTargets(g_gpu_device->GetWindowFormat(), g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(),
    443                  &progress);
    444   }
    445 
    446   // must be down here, because we need to compile first, triggered by CheckTargets()
    447   for (std::unique_ptr<Shader>& shader : m_stages)
    448     m_wants_depth_buffer |= shader->WantsDepthBuffer();
    449   m_needs_depth_buffer = m_enabled && m_wants_depth_buffer;
    450   if (m_wants_depth_buffer)
    451     DEV_LOG("Depth buffer is needed.");
    452 }
    453 
    454 void PostProcessing::Chain::ClearStages()
    455 {
    456   decltype(m_stages)().swap(m_stages);
    457 }
    458 
    459 void PostProcessing::Chain::UpdateSettings(std::unique_lock<std::mutex>& settings_lock)
    460 {
    461   SettingsInterface& si = GetLoadSettingsInterface(m_section);
    462 
    463   m_enabled = si.GetBoolValue(m_section, "Enabled", false);
    464 
    465   const u32 stage_count = Config::GetStageCount(si, m_section);
    466   if (stage_count == 0)
    467   {
    468     m_stages.clear();
    469     return;
    470   }
    471 
    472   Error error;
    473 
    474   m_stages.resize(stage_count);
    475 
    476   HostInterfaceProgressCallback progress;
    477   progress.SetProgressRange(stage_count);
    478 
    479   const GPUTexture::Format prev_format = m_target_format;
    480   m_wants_depth_buffer = false;
    481 
    482   for (u32 i = 0; i < stage_count; i++)
    483   {
    484     std::string stage_name = Config::GetStageShaderName(si, m_section, i);
    485     if (stage_name.empty())
    486     {
    487       error.SetString(fmt::format("No stage name in stage {}.", i + 1));
    488       ClearStagesWithError(error);
    489       return;
    490     }
    491 
    492     if (!m_stages[i] || stage_name != m_stages[i]->GetName())
    493     {
    494       if (i < m_stages.size())
    495         m_stages[i].reset();
    496 
    497       // Force recompile.
    498       m_target_format = GPUTexture::Format::Unknown;
    499 
    500       settings_lock.unlock();
    501 
    502       std::unique_ptr<Shader> shader = TryLoadingShader(stage_name, false, &error);
    503       if (!shader)
    504       {
    505         ClearStagesWithError(error);
    506         return;
    507       }
    508 
    509       if (i < m_stages.size())
    510         m_stages[i] = std::move(shader);
    511       else
    512         m_stages.push_back(std::move(shader));
    513 
    514       settings_lock.lock();
    515     }
    516 
    517     m_stages[i]->LoadOptions(si, GetStageConfigSection(m_section, i));
    518   }
    519 
    520   if (prev_format != GPUTexture::Format::Unknown)
    521     CheckTargets(prev_format, m_target_width, m_target_height, &progress);
    522 
    523   if (stage_count > 0)
    524   {
    525     s_timer.Reset();
    526     DEV_LOG("Loaded {} post-processing stages.", stage_count);
    527   }
    528 
    529   // must be down here, because we need to compile first, triggered by CheckTargets()
    530   for (std::unique_ptr<Shader>& shader : m_stages)
    531     m_wants_depth_buffer |= shader->WantsDepthBuffer();
    532   m_needs_depth_buffer = m_enabled && m_wants_depth_buffer;
    533   if (m_wants_depth_buffer)
    534     DEV_LOG("Depth buffer is needed.");
    535 }
    536 
    537 void PostProcessing::Chain::Toggle()
    538 {
    539   if (m_stages.empty())
    540   {
    541     Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER,
    542                             TRANSLATE_STR("OSDMessage", "No post-processing shaders are selected."),
    543                             Host::OSD_QUICK_DURATION);
    544     return;
    545   }
    546 
    547   const bool new_enabled = !m_enabled;
    548   Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER,
    549                           new_enabled ? TRANSLATE_STR("OSDMessage", "Post-processing is now enabled.") :
    550                                         TRANSLATE_STR("OSDMessage", "Post-processing is now disabled."),
    551                           Host::OSD_QUICK_DURATION);
    552   m_enabled = new_enabled;
    553   m_needs_depth_buffer = new_enabled && m_wants_depth_buffer;
    554   if (m_enabled)
    555     s_timer.Reset();
    556 }
    557 
    558 bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height,
    559                                          ProgressCallback* progress /* = nullptr */)
    560 {
    561   if (m_target_format == target_format && m_target_width == target_width && m_target_height == target_height)
    562     return true;
    563 
    564   // In case any allocs fail.
    565   DestroyTextures();
    566 
    567   if (!(m_input_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1,
    568                                                      GPUTexture::Type::RenderTarget, target_format)) ||
    569       !(m_output_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1,
    570                                                       GPUTexture::Type::RenderTarget, target_format)))
    571   {
    572     DestroyTextures();
    573     return false;
    574   }
    575 
    576   if (!progress)
    577     progress = ProgressCallback::NullProgressCallback;
    578 
    579   progress->SetProgressRange(static_cast<u32>(m_stages.size()));
    580   progress->SetProgressValue(0);
    581 
    582   m_wants_depth_buffer = false;
    583 
    584   for (size_t i = 0; i < m_stages.size(); i++)
    585   {
    586     Shader* const shader = m_stages[i].get();
    587 
    588     progress->FormatStatusText("Compiling {}...", shader->GetName());
    589 
    590     if (!shader->CompilePipeline(target_format, target_width, target_height, progress) ||
    591         !shader->ResizeOutput(target_format, target_width, target_height))
    592     {
    593       ERROR_LOG("Failed to compile one or more post-processing shaders, disabling.");
    594       Host::AddIconOSDMessage(
    595         "PostProcessLoadFail", ICON_FA_EXCLAMATION_TRIANGLE,
    596         fmt::format("Failed to compile post-processing shader '{}'. Disabling post-processing.", shader->GetName()));
    597       m_enabled = false;
    598       return false;
    599     }
    600 
    601     progress->SetProgressValue(static_cast<u32>(i + 1));
    602     m_wants_depth_buffer |= shader->WantsDepthBuffer();
    603   }
    604 
    605   m_target_format = target_format;
    606   m_target_width = target_width;
    607   m_target_height = target_height;
    608   m_needs_depth_buffer = m_enabled && m_wants_depth_buffer;
    609   return true;
    610 }
    611 
    612 void PostProcessing::Chain::DestroyTextures()
    613 {
    614   m_target_format = GPUTexture::Format::Unknown;
    615   m_target_width = 0;
    616   m_target_height = 0;
    617 
    618   g_gpu_device->RecycleTexture(std::move(m_output_texture));
    619   g_gpu_device->RecycleTexture(std::move(m_input_texture));
    620 }
    621 
    622 bool PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
    623                                   GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
    624                                   s32 native_height)
    625 {
    626   GL_SCOPE_FMT("{} Apply", m_section);
    627 
    628   GPUTexture* output = m_output_texture.get();
    629   input_color->MakeReadyForSampling();
    630   if (input_depth)
    631     input_depth->MakeReadyForSampling();
    632 
    633   for (const std::unique_ptr<Shader>& stage : m_stages)
    634   {
    635     const bool is_final = (stage.get() == m_stages.back().get());
    636 
    637     if (!stage->Apply(input_color, input_depth, is_final ? final_target : output, final_rect, orig_width, orig_height,
    638                       native_width, native_height, m_target_width, m_target_height))
    639     {
    640       return false;
    641     }
    642 
    643     if (!is_final)
    644     {
    645       output->MakeReadyForSampling();
    646       input_color = output;
    647       output = (output == m_output_texture.get()) ? m_input_texture.get() : m_output_texture.get();
    648     }
    649   }
    650 
    651   return true;
    652 }
    653 
    654 void PostProcessing::Initialize()
    655 {
    656   DisplayChain.LoadStages();
    657   InternalChain.LoadStages();
    658   s_timer.Reset();
    659 }
    660 
    661 void PostProcessing::UpdateSettings()
    662 {
    663   auto lock = Host::GetSettingsLock();
    664   ForAllChains([&lock](Chain& chain) { chain.UpdateSettings(lock); });
    665 }
    666 
    667 void PostProcessing::Shutdown()
    668 {
    669   g_gpu_device->RecycleTexture(std::move(s_dummy_texture));
    670   s_samplers.clear();
    671   ForAllChains([](Chain& chain) {
    672     chain.ClearStages();
    673     chain.DestroyTextures();
    674   });
    675 }
    676 
    677 bool PostProcessing::ReloadShaders()
    678 {
    679   if (!DisplayChain.HasStages() && !InternalChain.HasStages())
    680   {
    681     Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER,
    682                             TRANSLATE_STR("OSDMessage", "No post-processing shaders are selected."),
    683                             Host::OSD_QUICK_DURATION);
    684     return false;
    685   }
    686 
    687   ForAllChains([](Chain& chain) {
    688     chain.ClearStages();
    689     chain.DestroyTextures();
    690     chain.LoadStages();
    691   });
    692   s_timer.Reset();
    693 
    694   Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER,
    695                           TRANSLATE_STR("OSDMessage", "Post-processing shaders reloaded."), Host::OSD_QUICK_DURATION);
    696   return true;
    697 }
    698 
    699 std::unique_ptr<PostProcessing::Shader> PostProcessing::TryLoadingShader(const std::string& shader_name,
    700                                                                          bool only_config, Error* error)
    701 {
    702   std::string filename;
    703   std::optional<std::string> resource_str;
    704 
    705   // Try reshade first.
    706   filename = Path::Combine(
    707     EmuFolders::Shaders,
    708     fmt::format("reshade" FS_OSPATH_SEPARATOR_STR "Shaders" FS_OSPATH_SEPARATOR_STR "{}.fx", shader_name));
    709   if (FileSystem::FileExists(filename.c_str()))
    710   {
    711     std::unique_ptr<ReShadeFXShader> shader = std::make_unique<ReShadeFXShader>();
    712     if (shader->LoadFromFile(std::string(shader_name), filename.c_str(), only_config, error))
    713       return shader;
    714   }
    715 
    716   filename = Path::Combine(EmuFolders::Shaders, fmt::format("{}.glsl", shader_name));
    717   if (FileSystem::FileExists(filename.c_str()))
    718   {
    719     std::unique_ptr<GLSLShader> shader = std::make_unique<GLSLShader>();
    720     if (shader->LoadFromFile(std::string(shader_name), filename.c_str(), error))
    721       return shader;
    722   }
    723 
    724   filename =
    725     fmt::format("shaders/reshade" FS_OSPATH_SEPARATOR_STR "Shaders" FS_OSPATH_SEPARATOR_STR "{}.fx", shader_name);
    726   resource_str = Host::ReadResourceFileToString(filename.c_str(), true);
    727   if (resource_str.has_value())
    728   {
    729     std::unique_ptr<ReShadeFXShader> shader = std::make_unique<ReShadeFXShader>();
    730     if (shader->LoadFromString(std::string(shader_name), std::move(filename), std::move(resource_str.value()),
    731                                only_config, error))
    732     {
    733       return shader;
    734     }
    735   }
    736 
    737   filename = fmt::format("shaders" FS_OSPATH_SEPARATOR_STR "{}.glsl", shader_name);
    738   resource_str = Host::ReadResourceFileToString(filename.c_str(), true);
    739   if (resource_str.has_value())
    740   {
    741     std::unique_ptr<GLSLShader> shader = std::make_unique<GLSLShader>();
    742     if (shader->LoadFromString(std::string(shader_name), std::move(resource_str.value()), error))
    743       return shader;
    744   }
    745 
    746   ERROR_LOG("Failed to load shader '{}'", shader_name);
    747   return {};
    748 }
    749 
    750 SettingsInterface& PostProcessing::GetLoadSettingsInterface(const char* section)
    751 {
    752   // If PostProcessing/Enable is set in the game settings interface, use that.
    753   // Otherwise, use the base settings.
    754 
    755   SettingsInterface* game_si = Host::Internal::GetGameSettingsLayer();
    756   if (game_si && game_si->ContainsValue(section, "Enabled"))
    757     return *game_si;
    758   else
    759     return *Host::Internal::GetBaseSettingsLayer();
    760 }
    761 
    762 const Common::Timer& PostProcessing::GetTimer()
    763 {
    764   return s_timer;
    765 }
    766 
    767 GPUSampler* PostProcessing::GetSampler(const GPUSampler::Config& config)
    768 {
    769   auto it = s_samplers.find(config.key);
    770   if (it != s_samplers.end())
    771     return it->second.get();
    772 
    773   std::unique_ptr<GPUSampler> sampler = g_gpu_device->CreateSampler(config);
    774   if (!sampler)
    775     ERROR_LOG("Failed to create GPU sampler with config={:X}", config.key);
    776 
    777   it = s_samplers.emplace(config.key, std::move(sampler)).first;
    778   return it->second.get();
    779 }
    780 
    781 GPUTexture* PostProcessing::GetDummyTexture()
    782 {
    783   if (s_dummy_texture)
    784     return s_dummy_texture.get();
    785 
    786   const u32 zero = 0;
    787   s_dummy_texture = g_gpu_device->FetchTexture(1, 1, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
    788                                                &zero, sizeof(zero));
    789   if (!s_dummy_texture)
    790     ERROR_LOG("Failed to create dummy texture.");
    791 
    792   return s_dummy_texture.get();
    793 }