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_shader_fx.cpp (62975B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "postprocessing_shader_fx.h"
      5 #include "image.h"
      6 #include "input_manager.h"
      7 #include "shadergen.h"
      8 
      9 // TODO: Remove me
     10 #include "core/host.h"
     11 #include "core/settings.h"
     12 
     13 #include "common/assert.h"
     14 #include "common/bitutils.h"
     15 #include "common/error.h"
     16 #include "common/file_system.h"
     17 #include "common/log.h"
     18 #include "common/path.h"
     19 #include "common/progress_callback.h"
     20 #include "common/string_util.h"
     21 
     22 #include "effect_codegen.hpp"
     23 #include "effect_parser.hpp"
     24 #include "effect_preprocessor.hpp"
     25 
     26 #include "fmt/format.h"
     27 
     28 #include <bitset>
     29 #include <cctype>
     30 #include <cmath>
     31 #include <cstring>
     32 #include <sstream>
     33 
     34 Log_SetChannel(ReShadeFXShader);
     35 
     36 static constexpr s32 DEFAULT_BUFFER_WIDTH = 3840;
     37 static constexpr s32 DEFAULT_BUFFER_HEIGHT = 2160;
     38 
     39 static RenderAPI GetRenderAPI()
     40 {
     41   return g_gpu_device ? g_gpu_device->GetRenderAPI() : RenderAPI::D3D11;
     42 }
     43 
     44 static bool PreprocessorFileExistsCallback(const std::string& path)
     45 {
     46   if (Path::IsAbsolute(path))
     47     return FileSystem::FileExists(path.c_str());
     48 
     49   return Host::ResourceFileExists(path.c_str(), true);
     50 }
     51 
     52 static bool PreprocessorReadFileCallback(const std::string& path, std::string& data)
     53 {
     54   std::optional<std::string> rdata;
     55   if (Path::IsAbsolute(path))
     56     rdata = FileSystem::ReadFileToString(path.c_str());
     57   else
     58     rdata = Host::ReadResourceFileToString(path.c_str(), true);
     59   if (!rdata.has_value())
     60     return false;
     61 
     62   data = std::move(rdata.value());
     63   return true;
     64 }
     65 
     66 static std::unique_ptr<reshadefx::codegen> CreateRFXCodegen()
     67 {
     68   const bool debug_info = g_gpu_device ? g_gpu_device->IsDebugDevice() : false;
     69   const bool uniforms_to_spec_constants = false;
     70   const RenderAPI rapi = GetRenderAPI();
     71 
     72   switch (rapi)
     73   {
     74     case RenderAPI::None:
     75     case RenderAPI::D3D11:
     76     case RenderAPI::D3D12:
     77     {
     78       return std::unique_ptr<reshadefx::codegen>(
     79         reshadefx::create_codegen_hlsl(50, debug_info, uniforms_to_spec_constants));
     80     }
     81 
     82     case RenderAPI::Vulkan:
     83     case RenderAPI::Metal:
     84     {
     85       return std::unique_ptr<reshadefx::codegen>(reshadefx::create_codegen_glsl(
     86         false, true, debug_info, uniforms_to_spec_constants, false, (rapi == RenderAPI::Vulkan)));
     87     }
     88 
     89     case RenderAPI::OpenGL:
     90     case RenderAPI::OpenGLES:
     91     default:
     92     {
     93       return std::unique_ptr<reshadefx::codegen>(reshadefx::create_codegen_glsl(
     94         (rapi == RenderAPI::OpenGLES), false, debug_info, uniforms_to_spec_constants, false, true));
     95     }
     96   }
     97 }
     98 
     99 static GPUTexture::Format MapTextureFormat(reshadefx::texture_format format)
    100 {
    101   static constexpr GPUTexture::Format s_mapping[] = {
    102     GPUTexture::Format::Unknown, // unknown
    103     GPUTexture::Format::R8,      // r8
    104     GPUTexture::Format::R16,     // r16
    105     GPUTexture::Format::R16F,    // r16f
    106     GPUTexture::Format::R32I,    // r32i
    107     GPUTexture::Format::R32U,    // r32u
    108     GPUTexture::Format::R32F,    // r32f
    109     GPUTexture::Format::RG8,     // rg8
    110     GPUTexture::Format::RG16,    // rg16
    111     GPUTexture::Format::RG16F,   // rg16f
    112     GPUTexture::Format::RG32F,   // rg32f
    113     GPUTexture::Format::RGBA8,   // rgba8
    114     GPUTexture::Format::RGBA16,  // rgba16
    115     GPUTexture::Format::RGBA16F, // rgba16f
    116     GPUTexture::Format::RGBA32F, // rgba32f
    117     GPUTexture::Format::RGB10A2, // rgb10a2
    118   };
    119   DebugAssert(static_cast<u32>(format) < std::size(s_mapping));
    120   return s_mapping[static_cast<u32>(format)];
    121 }
    122 
    123 static GPUSampler::Config MapSampler(const reshadefx::sampler_info& si)
    124 {
    125   GPUSampler::Config config = GPUSampler::GetNearestConfig();
    126 
    127   switch (si.filter)
    128   {
    129     case reshadefx::filter_mode::min_mag_mip_point:
    130       config.min_filter = GPUSampler::Filter::Nearest;
    131       config.mag_filter = GPUSampler::Filter::Nearest;
    132       config.mip_filter = GPUSampler::Filter::Nearest;
    133       break;
    134 
    135     case reshadefx::filter_mode::min_mag_point_mip_linear:
    136       config.min_filter = GPUSampler::Filter::Nearest;
    137       config.mag_filter = GPUSampler::Filter::Nearest;
    138       config.mip_filter = GPUSampler::Filter::Linear;
    139       break;
    140 
    141     case reshadefx::filter_mode::min_point_mag_linear_mip_point:
    142       config.min_filter = GPUSampler::Filter::Linear;
    143       config.mag_filter = GPUSampler::Filter::Linear;
    144       config.mip_filter = GPUSampler::Filter::Nearest;
    145       break;
    146 
    147     case reshadefx::filter_mode::min_point_mag_mip_linear:
    148       config.min_filter = GPUSampler::Filter::Nearest;
    149       config.mag_filter = GPUSampler::Filter::Linear;
    150       config.mip_filter = GPUSampler::Filter::Linear;
    151       break;
    152 
    153     case reshadefx::filter_mode::min_linear_mag_mip_point:
    154       config.min_filter = GPUSampler::Filter::Linear;
    155       config.mag_filter = GPUSampler::Filter::Nearest;
    156       config.mip_filter = GPUSampler::Filter::Nearest;
    157       break;
    158 
    159     case reshadefx::filter_mode::min_linear_mag_point_mip_linear:
    160       config.min_filter = GPUSampler::Filter::Linear;
    161       config.mag_filter = GPUSampler::Filter::Nearest;
    162       config.mip_filter = GPUSampler::Filter::Linear;
    163       break;
    164 
    165     case reshadefx::filter_mode::min_mag_linear_mip_point:
    166       config.min_filter = GPUSampler::Filter::Linear;
    167       config.mag_filter = GPUSampler::Filter::Linear;
    168       config.mip_filter = GPUSampler::Filter::Nearest;
    169       break;
    170 
    171     case reshadefx::filter_mode::min_mag_mip_linear:
    172       config.min_filter = GPUSampler::Filter::Linear;
    173       config.mag_filter = GPUSampler::Filter::Linear;
    174       config.mip_filter = GPUSampler::Filter::Linear;
    175       break;
    176 
    177     default:
    178       break;
    179   }
    180 
    181   static constexpr auto map_address_mode = [](const reshadefx::texture_address_mode m) {
    182     switch (m)
    183     {
    184       case reshadefx::texture_address_mode::wrap:
    185         return GPUSampler::AddressMode::Repeat;
    186       case reshadefx::texture_address_mode::mirror:
    187         return GPUSampler::AddressMode::MirrorRepeat;
    188       case reshadefx::texture_address_mode::clamp:
    189         return GPUSampler::AddressMode::ClampToEdge;
    190       case reshadefx::texture_address_mode::border:
    191       default:
    192         return GPUSampler::AddressMode::ClampToBorder;
    193     }
    194   };
    195 
    196   config.address_u = map_address_mode(si.address_u);
    197   config.address_v = map_address_mode(si.address_v);
    198   config.address_w = map_address_mode(si.address_w);
    199 
    200   return config;
    201 }
    202 
    203 static GPUPipeline::BlendState MapBlendState(const reshadefx::pass_info& pi)
    204 {
    205   static constexpr auto map_blend_op = [](const reshadefx::pass_blend_op o) {
    206     switch (o)
    207     {
    208       case reshadefx::pass_blend_op::add:
    209         return GPUPipeline::BlendOp::Add;
    210       case reshadefx::pass_blend_op::subtract:
    211         return GPUPipeline::BlendOp::Subtract;
    212       case reshadefx::pass_blend_op::reverse_subtract:
    213         return GPUPipeline::BlendOp::ReverseSubtract;
    214       case reshadefx::pass_blend_op::min:
    215         return GPUPipeline::BlendOp::Min;
    216       case reshadefx::pass_blend_op::max:
    217       default:
    218         return GPUPipeline::BlendOp::Max;
    219     }
    220   };
    221   static constexpr auto map_blend_factor = [](const reshadefx::pass_blend_factor f) {
    222     switch (f)
    223     {
    224       case reshadefx::pass_blend_factor::zero:
    225         return GPUPipeline::BlendFunc::Zero;
    226       case reshadefx::pass_blend_factor::one:
    227         return GPUPipeline::BlendFunc::One;
    228       case reshadefx::pass_blend_factor::source_color:
    229         return GPUPipeline::BlendFunc::SrcColor;
    230       case reshadefx::pass_blend_factor::one_minus_source_color:
    231         return GPUPipeline::BlendFunc::InvSrcColor;
    232       case reshadefx::pass_blend_factor::dest_color:
    233         return GPUPipeline::BlendFunc::DstColor;
    234       case reshadefx::pass_blend_factor::one_minus_dest_color:
    235         return GPUPipeline::BlendFunc::InvDstColor;
    236       case reshadefx::pass_blend_factor::source_alpha:
    237         return GPUPipeline::BlendFunc::SrcAlpha;
    238       case reshadefx::pass_blend_factor::one_minus_source_alpha:
    239         return GPUPipeline::BlendFunc::InvSrcAlpha;
    240       case reshadefx::pass_blend_factor::dest_alpha:
    241       default:
    242         return GPUPipeline::BlendFunc::DstAlpha;
    243     }
    244   };
    245 
    246   GPUPipeline::BlendState bs = GPUPipeline::BlendState::GetNoBlendingState();
    247   bs.enable = (pi.blend_enable[0] != 0);
    248   bs.blend_op = map_blend_op(pi.blend_op[0]);
    249   bs.src_blend = map_blend_factor(pi.src_blend[0]);
    250   bs.dst_blend = map_blend_factor(pi.dest_blend[0]);
    251   bs.alpha_blend_op = map_blend_op(pi.blend_op_alpha[0]);
    252   bs.src_alpha_blend = map_blend_factor(pi.src_blend_alpha[0]);
    253   bs.dst_alpha_blend = map_blend_factor(pi.dest_blend_alpha[0]);
    254   bs.write_mask = pi.color_write_mask[0];
    255   return bs;
    256 }
    257 
    258 static GPUPipeline::Primitive MapPrimitive(reshadefx::primitive_topology topology)
    259 {
    260   switch (topology)
    261   {
    262     case reshadefx::primitive_topology::point_list:
    263       return GPUPipeline::Primitive::Points;
    264     case reshadefx::primitive_topology::line_list:
    265       return GPUPipeline::Primitive::Lines;
    266     case reshadefx::primitive_topology::line_strip:
    267       Panic("Unhandled line strip");
    268     case reshadefx::primitive_topology::triangle_list:
    269       return GPUPipeline::Primitive::Triangles;
    270     case reshadefx::primitive_topology::triangle_strip:
    271     default:
    272       return GPUPipeline::Primitive::TriangleStrips;
    273   }
    274 }
    275 
    276 PostProcessing::ReShadeFXShader::ReShadeFXShader() = default;
    277 
    278 PostProcessing::ReShadeFXShader::~ReShadeFXShader()
    279 {
    280   for (Texture& tex : m_textures)
    281     g_gpu_device->RecycleTexture(std::move(tex.texture));
    282 }
    283 
    284 bool PostProcessing::ReShadeFXShader::LoadFromFile(std::string name, std::string filename, bool only_config,
    285                                                    Error* error)
    286 {
    287   std::optional<std::string> data = FileSystem::ReadFileToString(filename.c_str(), error);
    288   if (!data.has_value())
    289   {
    290     ERROR_LOG("Failed to read '{}'.", filename);
    291     return false;
    292   }
    293 
    294   return LoadFromString(std::move(name), std::move(filename), std::move(data.value()), only_config, error);
    295 }
    296 
    297 bool PostProcessing::ReShadeFXShader::LoadFromString(std::string name, std::string filename, std::string code,
    298                                                      bool only_config, Error* error)
    299 {
    300   DebugAssert(only_config || g_gpu_device);
    301 
    302   m_name = std::move(name);
    303   m_filename = std::move(filename);
    304 
    305   // Reshade's preprocessor expects this.
    306   if (code.empty() || code.back() != '\n')
    307     code.push_back('\n');
    308 
    309   reshadefx::module temp_module;
    310   if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetWindowWidth(),
    311                     only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetWindowHeight(), &temp_module,
    312                     std::move(code), error))
    313   {
    314     return false;
    315   }
    316 
    317   if (!CreateOptions(temp_module, error))
    318     return false;
    319 
    320   // check limits
    321   if (!temp_module.techniques.empty())
    322   {
    323     bool has_passes = false;
    324     for (const reshadefx::technique_info& tech : temp_module.techniques)
    325     {
    326       for (const reshadefx::pass_info& pi : tech.passes)
    327       {
    328         has_passes = true;
    329 
    330         u32 max_rt = 0;
    331         for (u32 i = 0; i < std::size(pi.render_target_names); i++)
    332         {
    333           if (pi.render_target_names[i].empty())
    334             break;
    335 
    336           max_rt = std::max(max_rt, i);
    337         }
    338 
    339         if (max_rt > GPUDevice::MAX_RENDER_TARGETS)
    340         {
    341           Error::SetString(error, fmt::format("Too many render targets ({}) in pass {}, only {} are supported.", max_rt,
    342                                               pi.name, GPUDevice::MAX_RENDER_TARGETS));
    343           return false;
    344         }
    345 
    346         if (pi.samplers.size() > GPUDevice::MAX_TEXTURE_SAMPLERS)
    347         {
    348           Error::SetString(error, fmt::format("Too many samplers ({}) in pass {}, only {} are supported.",
    349                                               pi.samplers.size(), pi.name, GPUDevice::MAX_TEXTURE_SAMPLERS));
    350           return false;
    351         }
    352       }
    353     }
    354     if (!has_passes)
    355     {
    356       Error::SetString(error, "No passes defined in file.");
    357       return false;
    358     }
    359   }
    360 
    361   // Might go invalid when creating pipelines.
    362   m_valid = true;
    363   return true;
    364 }
    365 
    366 bool PostProcessing::ReShadeFXShader::IsValid() const
    367 {
    368   return m_valid;
    369 }
    370 
    371 bool PostProcessing::ReShadeFXShader::WantsDepthBuffer() const
    372 {
    373   return m_wants_depth_buffer;
    374 }
    375 
    376 bool PostProcessing::ReShadeFXShader::CreateModule(s32 buffer_width, s32 buffer_height, reshadefx::module* mod,
    377                                                    std::string code, Error* error)
    378 {
    379   reshadefx::preprocessor pp;
    380   pp.set_include_callbacks(PreprocessorFileExistsCallback, PreprocessorReadFileCallback);
    381 
    382   if (Path::IsAbsolute(m_filename))
    383   {
    384     // we're a real file, so include that directory
    385     pp.add_include_path(std::string(Path::GetDirectory(m_filename)));
    386   }
    387   else
    388   {
    389     // we're a resource, include the resource subdirectory, if there is one
    390     if (std::string_view resdir = Path::GetDirectory(m_filename); !resdir.empty())
    391       pp.add_include_path(std::string(resdir));
    392   }
    393 
    394   // root of the user directory, and resources
    395   pp.add_include_path(Path::Combine(EmuFolders::Shaders, "reshade" FS_OSPATH_SEPARATOR_STR "Shaders"));
    396   pp.add_include_path("shaders/reshade/Shaders");
    397 
    398   pp.add_macro_definition("__RESHADE__", "50901");
    399   pp.add_macro_definition("BUFFER_WIDTH", StringUtil::ToChars(buffer_width)); // TODO: can we make these uniforms?
    400   pp.add_macro_definition("BUFFER_HEIGHT", StringUtil::ToChars(buffer_height));
    401   pp.add_macro_definition("BUFFER_RCP_WIDTH", StringUtil::ToChars(1.0f / static_cast<float>(buffer_width)));
    402   pp.add_macro_definition("BUFFER_RCP_HEIGHT", StringUtil::ToChars(1.0f / static_cast<float>(buffer_height)));
    403   pp.add_macro_definition("BUFFER_COLOR_BIT_DEPTH", "32");
    404   pp.add_macro_definition("RESHADE_DEPTH_INPUT_IS_UPSIDE_DOWN", "0");
    405   pp.add_macro_definition("RESHADE_DEPTH_INPUT_IS_LOGARITHMIC", "0");
    406   pp.add_macro_definition("RESHADE_DEPTH_LINEARIZATION_FAR_PLANE", "1000.0");
    407   pp.add_macro_definition("RESHADE_DEPTH_INPUT_IS_REVERSED", "0");
    408 
    409   switch (GetRenderAPI())
    410   {
    411     case RenderAPI::D3D11:
    412     case RenderAPI::D3D12:
    413       pp.add_macro_definition("__RENDERER__", "0x0B000");
    414       break;
    415 
    416     case RenderAPI::OpenGL:
    417     case RenderAPI::OpenGLES:
    418     case RenderAPI::Vulkan:
    419     case RenderAPI::Metal:
    420       pp.add_macro_definition("__RENDERER__", "0x14300");
    421       break;
    422 
    423     default:
    424       UnreachableCode();
    425       break;
    426   }
    427 
    428   if (!pp.append_string(std::move(code), m_filename))
    429   {
    430     Error::SetString(error, fmt::format("Failed to preprocess:\n{}", pp.errors()));
    431     return false;
    432   }
    433 
    434   std::unique_ptr<reshadefx::codegen> cg = CreateRFXCodegen();
    435   if (!cg)
    436     return false;
    437 
    438   reshadefx::parser parser;
    439   if (!parser.parse(pp.output(), cg.get()))
    440   {
    441     Error::SetString(error, fmt::format("Failed to parse:\n{}", parser.errors()));
    442     return false;
    443   }
    444 
    445   cg->write_result(*mod);
    446   return true;
    447 }
    448 
    449 static bool HasAnnotationWithName(const reshadefx::uniform_info& uniform, const std::string_view annotation_name)
    450 {
    451   for (const reshadefx::annotation& an : uniform.annotations)
    452   {
    453     if (an.name == annotation_name)
    454       return true;
    455   }
    456 
    457   return false;
    458 }
    459 
    460 static std::string_view GetStringAnnotationValue(const std::vector<reshadefx::annotation>& annotations,
    461                                                  const std::string_view annotation_name,
    462                                                  const std::string_view default_value)
    463 {
    464   for (const reshadefx::annotation& an : annotations)
    465   {
    466     if (an.name != annotation_name)
    467       continue;
    468 
    469     if (an.type.base != reshadefx::type::t_string)
    470       continue;
    471 
    472     return an.value.string_data;
    473   }
    474 
    475   return default_value;
    476 }
    477 
    478 static bool GetBooleanAnnotationValue(const std::vector<reshadefx::annotation>& annotations,
    479                                       const std::string_view annotation_name, bool default_value)
    480 {
    481   for (const reshadefx::annotation& an : annotations)
    482   {
    483     if (an.name != annotation_name)
    484       continue;
    485 
    486     if (an.type.base != reshadefx::type::t_bool)
    487       continue;
    488 
    489     return (an.value.as_int[0] != 0);
    490   }
    491 
    492   return default_value;
    493 }
    494 
    495 static PostProcessing::ShaderOption::ValueVector
    496 GetVectorAnnotationValue(const reshadefx::uniform_info& uniform, const std::string_view annotation_name,
    497                          const PostProcessing::ShaderOption::ValueVector& default_value)
    498 {
    499   PostProcessing::ShaderOption::ValueVector vv = default_value;
    500   for (const reshadefx::annotation& an : uniform.annotations)
    501   {
    502     if (an.name != annotation_name)
    503       continue;
    504 
    505     const u32 components = std::min<u32>(an.type.components(), PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS);
    506 
    507     if (an.type.base == uniform.type.base || (an.type.is_integral() && uniform.type.is_integral())) // int<->uint
    508     {
    509       if (components > 0)
    510         std::memcpy(&vv[0].float_value, &an.value.as_float[0], sizeof(float) * components);
    511 
    512       break;
    513     }
    514     else if (an.type.base == reshadefx::type::t_string)
    515     {
    516       // Convert from string.
    517       if (uniform.type.base == reshadefx::type::t_float)
    518       {
    519         if (an.value.string_data == "BUFFER_WIDTH")
    520           vv[0].float_value = DEFAULT_BUFFER_WIDTH;
    521         else if (an.value.string_data == "BUFFER_HEIGHT")
    522           vv[0].float_value = DEFAULT_BUFFER_HEIGHT;
    523         else
    524           vv[0].float_value = StringUtil::FromChars<float>(an.value.string_data).value_or(1000.0f);
    525       }
    526       else if (uniform.type.base == reshadefx::type::t_int)
    527       {
    528         if (an.value.string_data == "BUFFER_WIDTH")
    529           vv[0].int_value = DEFAULT_BUFFER_WIDTH;
    530         else if (an.value.string_data == "BUFFER_HEIGHT")
    531           vv[0].int_value = DEFAULT_BUFFER_HEIGHT;
    532         else
    533           vv[0].int_value = StringUtil::FromChars<s32>(an.value.string_data).value_or(1000);
    534       }
    535       else
    536       {
    537         ERROR_LOG("Unhandled string value for '{}' (annotation type: {}, uniform type {})", uniform.name,
    538                   an.type.description(), uniform.type.description());
    539       }
    540 
    541       break;
    542     }
    543     else if (an.type.base == reshadefx::type::t_int)
    544     {
    545       // Convert from int.
    546       if (uniform.type.base == reshadefx::type::t_float)
    547       {
    548         for (u32 i = 0; i < components; i++)
    549           vv[i].float_value = static_cast<float>(an.value.as_int[i]);
    550       }
    551       else if (uniform.type.base == reshadefx::type::t_bool)
    552       {
    553         for (u32 i = 0; i < components; i++)
    554           vv[i].int_value = (an.value.as_int[i] != 0) ? 1 : 0;
    555       }
    556     }
    557     else if (an.type.base == reshadefx::type::t_float)
    558     {
    559       // Convert from float.
    560       if (uniform.type.base == reshadefx::type::t_int)
    561       {
    562         for (u32 i = 0; i < components; i++)
    563           vv[i].int_value = static_cast<int>(an.value.as_float[i]);
    564       }
    565       else if (uniform.type.base == reshadefx::type::t_bool)
    566       {
    567         for (u32 i = 0; i < components; i++)
    568           vv[i].int_value = (an.value.as_float[i] != 0.0f) ? 1 : 0;
    569       }
    570     }
    571 
    572     break;
    573   }
    574 
    575   return vv;
    576 }
    577 
    578 bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod, Error* error)
    579 {
    580   for (const reshadefx::uniform_info& ui : mod.uniforms)
    581   {
    582     SourceOptionType so;
    583     if (!GetSourceOption(ui, &so, error))
    584       return false;
    585     if (so != SourceOptionType::None)
    586     {
    587       DEV_LOG("Add source based option {} at offset {} ({})", static_cast<u32>(so), ui.offset, ui.name);
    588 
    589       SourceOption sopt;
    590       sopt.source = so;
    591       sopt.offset = ui.offset;
    592 
    593       const ShaderOption::ValueVector min =
    594         GetVectorAnnotationValue(ui, "min", ShaderOption::MakeFloatVector(0, 0, 0, 0));
    595       const ShaderOption::ValueVector max =
    596         GetVectorAnnotationValue(ui, "max", ShaderOption::MakeFloatVector(1, 1, 1, 1));
    597       const ShaderOption::ValueVector smoothing =
    598         GetVectorAnnotationValue(ui, "smoothing", ShaderOption::MakeFloatVector(0));
    599       const ShaderOption::ValueVector step =
    600         GetVectorAnnotationValue(ui, "step", ShaderOption::MakeFloatVector(0, 1, 0, 0));
    601 
    602       sopt.min = min[0].float_value;
    603       sopt.max = max[0].float_value;
    604       sopt.smoothing = smoothing[0].float_value;
    605       std::memcpy(&sopt.step[0], &step[0].float_value, sizeof(sopt.value));
    606       std::memcpy(&sopt.value[0], &ui.initializer_value.as_float[0], sizeof(sopt.value));
    607 
    608       m_source_options.push_back(std::move(sopt));
    609       continue;
    610     }
    611 
    612     ShaderOption opt;
    613     opt.name = ui.name;
    614     opt.category = GetStringAnnotationValue(ui.annotations, "ui_category", std::string_view());
    615     opt.tooltip = GetStringAnnotationValue(ui.annotations, "ui_tooltip", std::string_view());
    616 
    617     if (!GetBooleanAnnotationValue(ui.annotations, "hidden", false))
    618     {
    619       opt.ui_name = GetStringAnnotationValue(ui.annotations, "ui_label", std::string_view());
    620       if (opt.ui_name.empty())
    621         opt.ui_name = ui.name;
    622     }
    623 
    624     const std::string_view ui_type = GetStringAnnotationValue(ui.annotations, "ui_type", std::string_view());
    625 
    626     switch (ui.type.base)
    627     {
    628       case reshadefx::type::t_float:
    629         opt.type = ShaderOption::Type::Float;
    630         break;
    631 
    632       case reshadefx::type::t_int:
    633       case reshadefx::type::t_uint:
    634         opt.type = ShaderOption::Type::Int;
    635         break;
    636 
    637       case reshadefx::type::t_bool:
    638         opt.type = ShaderOption::Type::Bool;
    639         break;
    640 
    641       default:
    642         Error::SetString(error, fmt::format("Unhandled uniform type {} ({})", static_cast<u32>(ui.type.base), ui.name));
    643         return false;
    644     }
    645 
    646     opt.buffer_offset = ui.offset;
    647     opt.buffer_size = ui.size;
    648     opt.vector_size = ui.type.components();
    649     if (opt.vector_size == 0 || opt.vector_size > ShaderOption::MAX_VECTOR_COMPONENTS)
    650     {
    651       Error::SetString(error,
    652                        fmt::format("Unhandled vector size {} ({})", static_cast<u32>(ui.type.components()), ui.name));
    653       return false;
    654     }
    655 
    656     opt.min_value = GetVectorAnnotationValue(ui, "ui_min", opt.default_value);
    657     opt.max_value = GetVectorAnnotationValue(ui, "ui_max", opt.default_value);
    658     ShaderOption::ValueVector default_step = {};
    659     switch (opt.type)
    660     {
    661       case ShaderOption::Type::Float:
    662       {
    663         for (u32 i = 0; i < opt.vector_size; i++)
    664         {
    665           const float range = opt.max_value[i].float_value - opt.min_value[i].float_value;
    666           default_step[i].float_value = range / 100.0f;
    667         }
    668       }
    669       break;
    670 
    671       case ShaderOption::Type::Int:
    672       {
    673         for (u32 i = 0; i < opt.vector_size; i++)
    674         {
    675           const s32 range = opt.max_value[i].int_value - opt.min_value[i].int_value;
    676           default_step[i].int_value = std::max(range / 100, 1);
    677         }
    678       }
    679       break;
    680 
    681       default:
    682         break;
    683     }
    684     opt.step_value = GetVectorAnnotationValue(ui, "ui_step", default_step);
    685 
    686     // set a default maximum based on step if there isn't one
    687     if (!HasAnnotationWithName(ui, "ui_max") && HasAnnotationWithName(ui, "ui_step"))
    688     {
    689       for (u32 i = 0; i < opt.vector_size; i++)
    690       {
    691         switch (opt.type)
    692         {
    693           case ShaderOption::Type::Float:
    694             opt.max_value[i].float_value = opt.min_value[i].float_value + (opt.step_value[i].float_value * 100.0f);
    695             break;
    696           case ShaderOption::Type::Int:
    697             opt.max_value[i].int_value = opt.min_value[i].int_value + (opt.step_value[i].int_value * 100);
    698             break;
    699           default:
    700             break;
    701         }
    702       }
    703     }
    704 
    705     if (ui.has_initializer_value)
    706     {
    707       std::memcpy(&opt.default_value[0].float_value, &ui.initializer_value.as_float[0],
    708                   sizeof(float) * opt.vector_size);
    709     }
    710     else
    711     {
    712       opt.default_value = {};
    713     }
    714 
    715     // Assume default if user doesn't set it.
    716     opt.value = opt.default_value;
    717 
    718     if (!ui_type.empty() && opt.vector_size > 1)
    719     {
    720       WARNING_LOG("Uniform '{}' has UI type of '{}' but is vector not scalar ({}), ignoring", opt.name, ui_type,
    721                   opt.vector_size);
    722     }
    723     else if (!ui_type.empty())
    724     {
    725       if ((ui_type == "combo" || ui_type == "radio") && opt.type == ShaderOption::Type::Int)
    726       {
    727         const std::string_view ui_values = GetStringAnnotationValue(ui.annotations, "ui_items", std::string_view());
    728 
    729         size_t start_pos = 0;
    730         while (start_pos < ui_values.size())
    731         {
    732           size_t end_pos = start_pos;
    733           while (end_pos < ui_values.size() && ui_values[end_pos] != '\0')
    734             end_pos++;
    735 
    736           const size_t len = end_pos - start_pos;
    737           if (len > 0)
    738             opt.choice_options.emplace_back(ui_values.substr(start_pos, len));
    739           start_pos = end_pos + 1;
    740         }
    741 
    742         // update max if it hasn't been specified
    743         const size_t num_choices = opt.choice_options.size();
    744         if (num_choices > 0)
    745           opt.max_value[0].int_value = std::max(static_cast<s32>(num_choices - 1), opt.max_value[0].int_value);
    746       }
    747     }
    748 
    749     OptionList::iterator iter = std::find_if(m_options.begin(), m_options.end(),
    750                                              [&opt](const ShaderOption& it) { return it.category == opt.category; });
    751     if (iter != m_options.end())
    752     {
    753       // insert at the end of this category
    754       while (iter != m_options.end() && iter->category == opt.category)
    755         ++iter;
    756     }
    757     m_options.insert(iter, std::move(opt));
    758   }
    759 
    760   m_uniforms_size = mod.total_uniform_size;
    761   DEV_LOG("{}: {} options", m_filename, m_options.size());
    762   return true;
    763 }
    764 
    765 bool PostProcessing::ReShadeFXShader::GetSourceOption(const reshadefx::uniform_info& ui, SourceOptionType* si,
    766                                                       Error* error)
    767 {
    768   // TODO: Rewrite these to a lookup table instead, this if chain is terrible.
    769   const std::string_view source = GetStringAnnotationValue(ui.annotations, "source", {});
    770   if (!source.empty())
    771   {
    772     if (source == "timer")
    773     {
    774       if (ui.type.base != reshadefx::type::t_float || ui.type.components() > 1)
    775       {
    776         Error::SetString(
    777           error, fmt::format("Unexpected type '{}' for timer source in uniform '{}'", ui.type.description(), ui.name));
    778         return false;
    779       }
    780 
    781       *si = SourceOptionType::Timer;
    782       return true;
    783     }
    784     else if (source == "framecount")
    785     {
    786       if ((!ui.type.is_integral() && !ui.type.is_floating_point()) || ui.type.components() > 1)
    787       {
    788         Error::SetString(
    789           error, fmt::format("Unexpected type '{}' for timer source in uniform '{}'", ui.type.description(), ui.name));
    790         return false;
    791       }
    792 
    793       *si = (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::FrameCountF : SourceOptionType::FrameCount;
    794       return true;
    795     }
    796     else if (source == "frametime")
    797     {
    798       if (ui.type.base != reshadefx::type::t_float || ui.type.components() > 1)
    799       {
    800         Error::SetString(
    801           error, fmt::format("Unexpected type '{}' for timer source in uniform '{}'", ui.type.description(), ui.name));
    802         return false;
    803       }
    804 
    805       *si = SourceOptionType::FrameTime;
    806       return true;
    807     }
    808     else if (source == "pingpong")
    809     {
    810       if (!ui.type.is_floating_point() || ui.type.components() < 2)
    811       {
    812         Error::SetString(error, fmt::format("Unexpected type '{}' for pingpong source in uniform '{}'",
    813                                             ui.type.description(), ui.name));
    814         return false;
    815       }
    816 
    817       *si = SourceOptionType::PingPong;
    818       return true;
    819     }
    820     else if (source == "mousepoint")
    821     {
    822       if (!ui.type.is_floating_point() || ui.type.components() < 2)
    823       {
    824         Error::SetString(error, fmt::format("Unexpected type '{}' for mousepoint source in uniform '{}'",
    825                                             ui.type.description(), ui.name));
    826         return false;
    827       }
    828 
    829       *si = SourceOptionType::MousePoint;
    830       return true;
    831     }
    832     else if (source == "mousebutton")
    833     {
    834       WARNING_LOG("Ignoring mousebutton source in uniform '{}', not supported.", ui.name);
    835       *si = SourceOptionType::Zero;
    836       return true;
    837     }
    838     else if (source == "random")
    839     {
    840       if ((!ui.type.is_floating_point() && !ui.type.is_integral()) || ui.type.components() != 1)
    841       {
    842         Error::SetString(error, fmt::format("Unexpected type '{}' ({} components) for random source in uniform '{}'",
    843                                             ui.type.description(), ui.type.components(), ui.name));
    844         return false;
    845       }
    846 
    847       // TODO: This is missing min/max handling.
    848       *si = (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::RandomF : SourceOptionType::Random;
    849       return true;
    850     }
    851     else if (source == "overlay_active")
    852     {
    853       *si = SourceOptionType::Zero;
    854       return true;
    855     }
    856     else if (source == "has_depth")
    857     {
    858       *si = SourceOptionType::HasDepth;
    859       return true;
    860     }
    861     else if (source == "bufferwidth")
    862     {
    863       *si = (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::BufferWidthF : SourceOptionType::BufferWidth;
    864       return true;
    865     }
    866     else if (source == "bufferheight")
    867     {
    868       *si =
    869         (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::BufferHeightF : SourceOptionType::BufferHeight;
    870       return true;
    871     }
    872     else if (source == "internalwidth")
    873     {
    874       *si =
    875         (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::InternalWidthF : SourceOptionType::InternalWidth;
    876       return true;
    877     }
    878     else if (source == "internalheight")
    879     {
    880       *si = (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::InternalHeightF :
    881                                                          SourceOptionType::InternalHeight;
    882       return true;
    883     }
    884     else if (source == "nativewidth")
    885     {
    886       *si = (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::NativeWidthF : SourceOptionType::NativeWidth;
    887       return true;
    888     }
    889     else if (source == "nativeheight")
    890     {
    891       *si =
    892         (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::NativeHeightF : SourceOptionType::NativeHeight;
    893       return true;
    894     }
    895     else if (source == "upscale_multiplier")
    896     {
    897       if (!ui.type.is_floating_point() || ui.type.components() != 1)
    898       {
    899         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    900                                             source, ui.name));
    901         return false;
    902       }
    903 
    904       *si = SourceOptionType::UpscaleMultiplier;
    905       return true;
    906     }
    907     else if (source == "viewportx")
    908     {
    909       if (!ui.type.is_floating_point() || ui.type.components() != 1)
    910       {
    911         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    912                                             source, ui.name));
    913         return false;
    914       }
    915 
    916       *si = SourceOptionType::ViewportX;
    917       return true;
    918     }
    919     else if (source == "viewporty")
    920     {
    921       if (!ui.type.is_floating_point() || ui.type.components() != 1)
    922       {
    923         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    924                                             source, ui.name));
    925         return false;
    926       }
    927 
    928       *si = SourceOptionType::ViewportY;
    929       return true;
    930     }
    931     else if (source == "viewportwidth")
    932     {
    933       if (!ui.type.is_floating_point() || ui.type.components() != 1)
    934       {
    935         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    936                                             source, ui.name));
    937         return false;
    938       }
    939 
    940       *si = SourceOptionType::ViewportWidth;
    941       return true;
    942     }
    943     else if (source == "viewportheight")
    944     {
    945       if (!ui.type.is_floating_point() || ui.type.components() != 1)
    946       {
    947         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    948                                             source, ui.name));
    949         return false;
    950       }
    951 
    952       *si = SourceOptionType::ViewportHeight;
    953       return true;
    954     }
    955     else if (source == "viewportoffset")
    956     {
    957       if (!ui.type.is_floating_point() || ui.type.components() != 2)
    958       {
    959         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    960                                             source, ui.name));
    961         return false;
    962       }
    963 
    964       *si = SourceOptionType::ViewportOffset;
    965       return true;
    966     }
    967     else if (source == "viewportsize")
    968     {
    969       if (!ui.type.is_floating_point() || ui.type.components() != 2)
    970       {
    971         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    972                                             source, ui.name));
    973         return false;
    974       }
    975 
    976       *si = SourceOptionType::ViewportSize;
    977       return true;
    978     }
    979     else if (source == "internal_pixel_size")
    980     {
    981       if (!ui.type.is_floating_point() || ui.type.components() != 2)
    982       {
    983         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    984                                             source, ui.name));
    985         return false;
    986       }
    987 
    988       *si = SourceOptionType::InternalPixelSize;
    989       return true;
    990     }
    991     else if (source == "normalized_internal_pixel_size")
    992     {
    993       if (!ui.type.is_floating_point() || ui.type.components() != 2)
    994       {
    995         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
    996                                             source, ui.name));
    997         return false;
    998       }
    999 
   1000       *si = SourceOptionType::InternalNormPixelSize;
   1001       return true;
   1002     }
   1003     else if (source == "native_pixel_size")
   1004     {
   1005       if (!ui.type.is_floating_point() || ui.type.components() != 2)
   1006       {
   1007         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
   1008                                             source, ui.name));
   1009         return false;
   1010       }
   1011 
   1012       *si = SourceOptionType::NativePixelSize;
   1013       return true;
   1014     }
   1015     else if (source == "normalized_native_pixel_size")
   1016     {
   1017       if (!ui.type.is_floating_point() || ui.type.components() != 2)
   1018       {
   1019         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
   1020                                             source, ui.name));
   1021         return false;
   1022       }
   1023 
   1024       *si = SourceOptionType::NativeNormPixelSize;
   1025       return true;
   1026     }
   1027     else if (source == "buffer_to_viewport_ratio")
   1028     {
   1029       if (!ui.type.is_floating_point() || ui.type.components() != 2)
   1030       {
   1031         Error::SetString(error, fmt::format("Unexpected type '{}' for {} source in uniform '{}'", ui.type.description(),
   1032                                             source, ui.name));
   1033         return false;
   1034       }
   1035 
   1036       *si = SourceOptionType::BufferToViewportRatio;
   1037       return true;
   1038     }
   1039     else
   1040     {
   1041       Error::SetString(error, fmt::format("Unknown source '{}' in uniform '{}'", source, ui.name));
   1042       return false;
   1043     }
   1044   }
   1045 
   1046   if (ui.has_initializer_value)
   1047   {
   1048     if (ui.initializer_value.string_data == "BUFFER_WIDTH")
   1049     {
   1050       *si = (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::BufferWidthF : SourceOptionType::BufferWidth;
   1051       return true;
   1052     }
   1053     else if (ui.initializer_value.string_data == "BUFFER_HEIGHT")
   1054     {
   1055       *si =
   1056         (ui.type.base == reshadefx::type::t_float) ? SourceOptionType::BufferHeightF : SourceOptionType::BufferHeight;
   1057       return true;
   1058     }
   1059   }
   1060 
   1061   *si = SourceOptionType::None;
   1062   return true;
   1063 }
   1064 
   1065 bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer_format, reshadefx::module& mod,
   1066                                                    Error* error)
   1067 {
   1068   u32 total_passes = 0;
   1069   for (const reshadefx::technique_info& tech : mod.techniques)
   1070     total_passes += static_cast<u32>(tech.passes.size());
   1071   if (total_passes == 0)
   1072   {
   1073     Error::SetString(error, "No passes defined.");
   1074     return false;
   1075   }
   1076 
   1077   m_passes.reserve(total_passes);
   1078 
   1079   // Named render targets.
   1080   for (const reshadefx::texture_info& ti : mod.textures)
   1081   {
   1082     Texture tex;
   1083 
   1084     if (!ti.semantic.empty())
   1085     {
   1086       DEV_LOG("Ignoring semantic {} texture {}", ti.semantic, ti.unique_name);
   1087       continue;
   1088     }
   1089     if (ti.render_target)
   1090     {
   1091       tex.rt_scale = 1.0f;
   1092       tex.format = MapTextureFormat(ti.format);
   1093       DEV_LOG("Creating render target '{}' {}", ti.unique_name, GPUTexture::GetFormatName(tex.format));
   1094     }
   1095     else
   1096     {
   1097       const std::string_view source = GetStringAnnotationValue(ti.annotations, "source", {});
   1098       if (source.empty())
   1099       {
   1100         Error::SetString(error, fmt::format("Non-render target texture '{}' is missing source.", ti.unique_name));
   1101         return false;
   1102       }
   1103 
   1104       RGBA8Image image;
   1105       if (const std::string image_path =
   1106             Path::Combine(EmuFolders::Shaders, Path::Combine("reshade" FS_OSPATH_SEPARATOR_STR "Textures", source));
   1107           !image.LoadFromFile(image_path.c_str()))
   1108       {
   1109         // Might be a base file/resource instead.
   1110         const std::string resource_name = Path::Combine("shaders/reshade/Textures", source);
   1111         if (std::optional<DynamicHeapArray<u8>> resdata = Host::ReadResourceFile(resource_name.c_str(), true);
   1112             !resdata.has_value() || !image.LoadFromBuffer(resource_name.c_str(), resdata->data(), resdata->size()))
   1113         {
   1114           Error::SetString(error, fmt::format("Failed to load image '{}' (from '{}')", source, image_path).c_str());
   1115           return false;
   1116         }
   1117       }
   1118 
   1119       tex.rt_scale = 0.0f;
   1120       tex.texture = g_gpu_device->FetchTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
   1121                                                GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch());
   1122       if (!tex.texture)
   1123       {
   1124         Error::SetString(
   1125           error, fmt::format("Failed to create {}x{} texture ({})", image.GetWidth(), image.GetHeight(), source));
   1126         return false;
   1127       }
   1128 
   1129       DEV_LOG("Loaded {}x{} texture ({})", image.GetWidth(), image.GetHeight(), source);
   1130     }
   1131 
   1132     tex.reshade_name = ti.unique_name;
   1133     m_textures.push_back(std::move(tex));
   1134   }
   1135 
   1136   for (reshadefx::technique_info& tech : mod.techniques)
   1137   {
   1138     for (reshadefx::pass_info& pi : tech.passes)
   1139     {
   1140       const bool is_final = (&tech == &mod.techniques.back() && &pi == &tech.passes.back());
   1141 
   1142       Pass pass;
   1143       pass.num_vertices = pi.num_vertices;
   1144 
   1145       if (is_final)
   1146       {
   1147         pass.render_targets.push_back(OUTPUT_COLOR_TEXTURE);
   1148       }
   1149       else if (!pi.render_target_names[0].empty())
   1150       {
   1151         for (const std::string& rtname : pi.render_target_names)
   1152         {
   1153           if (rtname.empty())
   1154             break;
   1155 
   1156           TextureID rt = static_cast<TextureID>(m_textures.size());
   1157           for (u32 i = 0; i < static_cast<u32>(m_textures.size()); i++)
   1158           {
   1159             if (m_textures[i].reshade_name == rtname)
   1160             {
   1161               rt = static_cast<TextureID>(i);
   1162               break;
   1163             }
   1164           }
   1165           if (rt == static_cast<TextureID>(m_textures.size()))
   1166           {
   1167             Error::SetString(error,
   1168                              fmt::format("Unknown texture '{}' used as render target in pass '{}'", rtname, pi.name));
   1169             return false;
   1170           }
   1171 
   1172           pass.render_targets.push_back(rt);
   1173         }
   1174       }
   1175       else
   1176       {
   1177         Texture new_rt;
   1178         new_rt.rt_scale = 1.0f;
   1179         new_rt.format = backbuffer_format;
   1180         pass.render_targets.push_back(static_cast<TextureID>(m_textures.size()));
   1181         m_textures.push_back(std::move(new_rt));
   1182       }
   1183 
   1184       u32 texture_slot = 0;
   1185       for (const reshadefx::sampler_info& si : pi.samplers)
   1186       {
   1187         Sampler sampler;
   1188         sampler.slot = texture_slot++;
   1189         sampler.reshade_name = si.unique_name;
   1190 
   1191         sampler.texture_id = static_cast<TextureID>(m_textures.size());
   1192         for (const reshadefx::texture_info& ti : mod.textures)
   1193         {
   1194           if (ti.unique_name == si.texture_name)
   1195           {
   1196             // found the texture, now look for our side of it
   1197             if (ti.semantic == "COLOR")
   1198             {
   1199               sampler.texture_id = INPUT_COLOR_TEXTURE;
   1200               break;
   1201             }
   1202             else if (ti.semantic == "DEPTH")
   1203             {
   1204               sampler.texture_id = INPUT_DEPTH_TEXTURE;
   1205               m_wants_depth_buffer = true;
   1206               break;
   1207             }
   1208             else if (!ti.semantic.empty())
   1209             {
   1210               Error::SetString(error, fmt::format("Unknown semantic {} in texture {}", ti.semantic, ti.name));
   1211               return false;
   1212             }
   1213 
   1214             // must be a render target, or another texture
   1215             for (u32 i = 0; i < static_cast<u32>(m_textures.size()); i++)
   1216             {
   1217               if (m_textures[i].reshade_name == si.texture_name)
   1218               {
   1219                 // hook it up
   1220                 sampler.texture_id = static_cast<TextureID>(i);
   1221                 break;
   1222               }
   1223             }
   1224 
   1225             break;
   1226           }
   1227         }
   1228         if (sampler.texture_id == static_cast<TextureID>(m_textures.size()))
   1229         {
   1230           Error::SetString(
   1231             error, fmt::format("Unknown texture {} (sampler {}) in pass {}", si.texture_name, si.name, pi.name));
   1232           return false;
   1233         }
   1234 
   1235         DEV_LOG("Pass {} Texture {} => {}", pi.name, si.texture_name, sampler.texture_id);
   1236 
   1237         sampler.sampler = GetSampler(MapSampler(si));
   1238         if (!sampler.sampler)
   1239         {
   1240           Error::SetString(error, "Failed to create sampler.");
   1241           return false;
   1242         }
   1243 
   1244         pass.samplers.push_back(std::move(sampler));
   1245       }
   1246 
   1247 #ifdef _DEBUG
   1248       pass.name = std::move(pi.name);
   1249 #endif
   1250       m_passes.push_back(std::move(pass));
   1251     }
   1252   }
   1253 
   1254   return true;
   1255 }
   1256 
   1257 const char* PostProcessing::ReShadeFXShader::GetTextureNameForID(TextureID id) const
   1258 {
   1259   if (id == INPUT_COLOR_TEXTURE)
   1260     return "Input Color Texture / Backbuffer";
   1261   else if (id == INPUT_DEPTH_TEXTURE)
   1262     return "Input Depth Texture";
   1263   else if (id == OUTPUT_COLOR_TEXTURE)
   1264     return "Output Color Texture";
   1265   else if (id < 0 || static_cast<size_t>(id) >= m_textures.size())
   1266     return "UNKNOWN";
   1267   else
   1268     return m_textures[static_cast<size_t>(id)].reshade_name.c_str();
   1269 }
   1270 
   1271 GPUTexture* PostProcessing::ReShadeFXShader::GetTextureByID(TextureID id, GPUTexture* input_color,
   1272                                                             GPUTexture* input_depth, GPUTexture* final_target) const
   1273 {
   1274   if (id < 0)
   1275   {
   1276     if (id == INPUT_COLOR_TEXTURE)
   1277     {
   1278       return input_color;
   1279     }
   1280     else if (id == INPUT_DEPTH_TEXTURE)
   1281     {
   1282       return input_depth ? input_depth : GetDummyTexture();
   1283     }
   1284     else if (id == OUTPUT_COLOR_TEXTURE)
   1285     {
   1286       return final_target;
   1287     }
   1288     else
   1289     {
   1290       Panic("Unexpected reserved texture ID");
   1291       return nullptr;
   1292     }
   1293   }
   1294 
   1295   if (static_cast<size_t>(id) >= m_textures.size())
   1296     Panic("Unexpected texture ID");
   1297 
   1298   return m_textures[static_cast<size_t>(id)].texture.get();
   1299 }
   1300 
   1301 bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format, u32 width, u32 height,
   1302                                                       ProgressCallback* progress)
   1303 {
   1304   const RenderAPI api = g_gpu_device->GetRenderAPI();
   1305   const bool needs_main_defn = (api != RenderAPI::D3D11 && api != RenderAPI::D3D12);
   1306 
   1307   m_valid = false;
   1308   m_textures.clear();
   1309   m_passes.clear();
   1310   m_wants_depth_buffer = false;
   1311 
   1312   std::string fxcode;
   1313   if (!PreprocessorReadFileCallback(m_filename, fxcode))
   1314   {
   1315     ERROR_LOG("Failed to re-read shader for pipeline: '{}'", m_filename);
   1316     return false;
   1317   }
   1318 
   1319   // Reshade's preprocessor expects this.
   1320   if (fxcode.empty() || fxcode.back() != '\n')
   1321     fxcode.push_back('\n');
   1322 
   1323   Error error;
   1324   reshadefx::module mod;
   1325   if (!CreateModule(width, height, &mod, std::move(fxcode), &error))
   1326   {
   1327     ERROR_LOG("Failed to create module for '{}': {}", m_name, error.GetDescription());
   1328     return false;
   1329   }
   1330 
   1331   DebugAssert(!mod.techniques.empty());
   1332 
   1333   if (!CreatePasses(format, mod, &error))
   1334   {
   1335     ERROR_LOG("Failed to create passes for '{}': {}", m_name, error.GetDescription());
   1336     return false;
   1337   }
   1338 
   1339   const std::string_view code(mod.code.data(), mod.code.size());
   1340 
   1341   auto get_shader = [api, needs_main_defn, &code](const std::string& name, const std::span<Sampler> samplers,
   1342                                                   GPUShaderStage stage) {
   1343     std::string real_code;
   1344     if (needs_main_defn)
   1345     {
   1346       // dFdx/dFdy are not defined in the vertex shader.
   1347       const char* defns =
   1348         (stage == GPUShaderStage::Vertex) ? "#define dFdx(x) x\n#define dFdy(x) x\n#define discard\n" : "";
   1349       const char* precision = (api == RenderAPI::OpenGLES) ?
   1350                                 "precision highp float;\nprecision highp int;\nprecision highp sampler2D;\n" :
   1351                                 "";
   1352 
   1353       TinyString version_string = "#version 460 core\n";
   1354 #ifdef ENABLE_OPENGL
   1355       if (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES)
   1356         version_string = ShaderGen::GetGLSLVersionString(api, ShaderGen::GetGLSLVersion(api));
   1357 #endif
   1358       real_code = fmt::format("{}\n#define ENTRY_POINT_{}\n{}\n{}\n{}", version_string, name, defns, precision, code);
   1359 
   1360       for (const Sampler& sampler : samplers)
   1361       {
   1362         std::string decl = fmt::format("binding = /*SAMPLER:{}*/0", sampler.reshade_name);
   1363         std::string replacement = fmt::format("binding = {}", sampler.slot);
   1364         StringUtil::ReplaceAll(&real_code, decl, replacement);
   1365       }
   1366     }
   1367     else
   1368     {
   1369       real_code = std::string(code);
   1370 
   1371       for (const Sampler& sampler : samplers)
   1372       {
   1373         std::string decl = fmt::format("__{}_t : register( t0);", sampler.reshade_name);
   1374         std::string replacement =
   1375           fmt::format("__{}_t : register({}t{});", sampler.reshade_name, (sampler.slot < 10) ? " " : "", sampler.slot);
   1376         StringUtil::ReplaceAll(&real_code, decl, replacement);
   1377 
   1378         decl = fmt::format("__{}_s : register( s0);", sampler.reshade_name);
   1379         replacement =
   1380           fmt::format("__{}_s : register({}s{});", sampler.reshade_name, (sampler.slot < 10) ? " " : "", sampler.slot);
   1381         StringUtil::ReplaceAll(&real_code, decl, replacement);
   1382       }
   1383     }
   1384 
   1385     // FileSystem::WriteStringToFile("D:\\foo.txt", real_code);
   1386 
   1387     Error error;
   1388     std::unique_ptr<GPUShader> sshader = g_gpu_device->CreateShader(
   1389       stage, ShaderGen::GetShaderLanguageForAPI(api), real_code, &error, needs_main_defn ? "main" : name.c_str());
   1390     if (!sshader)
   1391       ERROR_LOG("Failed to compile function '{}': {}", name, error.GetDescription());
   1392 
   1393     return sshader;
   1394   };
   1395 
   1396   GPUPipeline::GraphicsConfig plconfig;
   1397   plconfig.layout = GPUPipeline::Layout::MultiTextureAndUBO;
   1398   plconfig.primitive = GPUPipeline::Primitive::Triangles;
   1399   plconfig.depth_format = GPUTexture::Format::Unknown;
   1400   plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState();
   1401   plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
   1402   plconfig.blend = GPUPipeline::BlendState::GetNoBlendingState();
   1403   plconfig.samples = 1;
   1404   plconfig.per_sample_shading = false;
   1405   plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags;
   1406 
   1407   progress->PushState();
   1408 
   1409   size_t total_passes = 0;
   1410   for (const reshadefx::technique_info& tech : mod.techniques)
   1411     total_passes += tech.passes.size();
   1412   progress->SetProgressRange(static_cast<u32>(total_passes));
   1413   progress->SetProgressValue(0);
   1414 
   1415   u32 passnum = 0;
   1416   for (const reshadefx::technique_info& tech : mod.techniques)
   1417   {
   1418     for (const reshadefx::pass_info& info : tech.passes)
   1419     {
   1420       DebugAssert(passnum < m_passes.size());
   1421       Pass& pass = m_passes[passnum++];
   1422 
   1423       auto vs = get_shader(info.vs_entry_point, pass.samplers, GPUShaderStage::Vertex);
   1424       auto fs = get_shader(info.ps_entry_point, pass.samplers, GPUShaderStage::Fragment);
   1425       if (!vs || !fs)
   1426       {
   1427         progress->PopState();
   1428         return false;
   1429       }
   1430 
   1431       for (size_t i = 0; i < pass.render_targets.size(); i++)
   1432       {
   1433         plconfig.color_formats[i] =
   1434           ((pass.render_targets[i] >= 0) ? m_textures[pass.render_targets[i]].format : format);
   1435       }
   1436       for (size_t i = pass.render_targets.size(); i < GPUDevice::MAX_RENDER_TARGETS; i++)
   1437         plconfig.color_formats[i] = GPUTexture::Format::Unknown;
   1438       plconfig.depth_format = GPUTexture::Format::Unknown;
   1439 
   1440       plconfig.blend = MapBlendState(info);
   1441       plconfig.primitive = MapPrimitive(info.topology);
   1442       plconfig.vertex_shader = vs.get();
   1443       plconfig.fragment_shader = fs.get();
   1444       plconfig.geometry_shader = nullptr;
   1445       if (!plconfig.vertex_shader || !plconfig.fragment_shader)
   1446       {
   1447         progress->PopState();
   1448         return false;
   1449       }
   1450 
   1451       pass.pipeline = g_gpu_device->CreatePipeline(plconfig);
   1452       if (!pass.pipeline)
   1453       {
   1454         ERROR_LOG("Failed to create pipeline for pass '{}'", info.name);
   1455         progress->PopState();
   1456         return false;
   1457       }
   1458 
   1459       progress->SetProgressValue(passnum);
   1460     }
   1461   }
   1462 
   1463   progress->PopState();
   1464 
   1465   m_valid = true;
   1466   return true;
   1467 }
   1468 
   1469 bool PostProcessing::ReShadeFXShader::ResizeOutput(GPUTexture::Format format, u32 width, u32 height)
   1470 {
   1471   m_valid = false;
   1472 
   1473   for (Texture& tex : m_textures)
   1474   {
   1475     if (tex.rt_scale == 0.0f)
   1476       continue;
   1477 
   1478     g_gpu_device->RecycleTexture(std::move(tex.texture));
   1479 
   1480     const u32 t_width = std::max(static_cast<u32>(static_cast<float>(width) * tex.rt_scale), 1u);
   1481     const u32 t_height = std::max(static_cast<u32>(static_cast<float>(height) * tex.rt_scale), 1u);
   1482     tex.texture = g_gpu_device->FetchTexture(t_width, t_height, 1, 1, 1, GPUTexture::Type::RenderTarget, tex.format);
   1483     if (!tex.texture)
   1484     {
   1485       ERROR_LOG("Failed to create {}x{} texture", t_width, t_height);
   1486       return {};
   1487     }
   1488   }
   1489 
   1490   m_valid = true;
   1491   return true;
   1492 }
   1493 
   1494 bool PostProcessing::ReShadeFXShader::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
   1495                                             GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
   1496                                             s32 native_height, u32 target_width, u32 target_height)
   1497 {
   1498   GL_PUSH_FMT("PostProcessingShaderFX {}", m_name);
   1499 
   1500   m_frame_count++;
   1501 
   1502   // Reshade always draws at full size.
   1503   g_gpu_device->SetViewportAndScissor(GSVector4i(0, 0, target_width, target_height));
   1504 
   1505   if (m_uniforms_size > 0)
   1506   {
   1507     GL_SCOPE_FMT("Uniforms: {} bytes", m_uniforms_size);
   1508 
   1509     u8* uniforms = static_cast<u8*>(g_gpu_device->MapUniformBuffer(m_uniforms_size));
   1510     for (const ShaderOption& opt : m_options)
   1511     {
   1512       DebugAssert((opt.buffer_offset + opt.buffer_size) <= m_uniforms_size);
   1513       std::memcpy(uniforms + opt.buffer_offset, &opt.value[0].float_value, opt.buffer_size);
   1514     }
   1515     for (const SourceOption& so : m_source_options)
   1516     {
   1517       u8* dst = uniforms + so.offset;
   1518       switch (so.source)
   1519       {
   1520         case SourceOptionType::Zero:
   1521         {
   1522           const u32 value = 0;
   1523           std::memcpy(dst, &value, sizeof(value));
   1524         }
   1525         break;
   1526 
   1527         case SourceOptionType::HasDepth:
   1528         {
   1529           const u32 value = BoolToUInt32(input_depth != nullptr);
   1530           std::memcpy(dst, &value, sizeof(value));
   1531         }
   1532         break;
   1533 
   1534         case SourceOptionType::Timer:
   1535         {
   1536           const float value = static_cast<float>(PostProcessing::GetTimer().GetTimeMilliseconds());
   1537           std::memcpy(dst, &value, sizeof(value));
   1538         }
   1539         break;
   1540 
   1541         case SourceOptionType::FrameTime:
   1542         {
   1543           const float value = static_cast<float>(m_frame_timer.GetTimeMilliseconds());
   1544           std::memcpy(dst, &value, sizeof(value));
   1545         }
   1546         break;
   1547 
   1548         case SourceOptionType::FrameCount:
   1549         {
   1550           std::memcpy(dst, &m_frame_count, sizeof(m_frame_count));
   1551         }
   1552         break;
   1553 
   1554         case SourceOptionType::FrameCountF:
   1555         {
   1556           const float value = static_cast<float>(m_frame_count);
   1557           std::memcpy(dst, &value, sizeof(value));
   1558         }
   1559         break;
   1560 
   1561         case SourceOptionType::PingPong:
   1562         {
   1563           float increment = so.step[1] == 0 ?
   1564                               so.step[0] :
   1565                               (so.step[0] + std::fmod(static_cast<float>(std::rand()), so.step[1] - so.step[0] + 1));
   1566 
   1567           std::array<float, 2> value = {so.value[0].float_value, so.value[1].float_value};
   1568           if (value[1] >= 0)
   1569           {
   1570             increment = std::max(increment - std::max(0.0f, so.smoothing - (so.max - value[0])), 0.05f);
   1571             increment *= static_cast<float>(m_frame_timer.GetTimeMilliseconds() * 1e-9);
   1572 
   1573             if ((value[0] += increment) >= so.max)
   1574             {
   1575               value[0] = so.max;
   1576               value[1] = -1;
   1577             }
   1578           }
   1579           else
   1580           {
   1581             increment = std::max(increment - std::max(0.0f, so.smoothing - (value[0] - so.min)), 0.05f);
   1582             increment *= static_cast<float>(m_frame_timer.GetTimeMilliseconds() * 1e-9);
   1583 
   1584             if ((value[0] -= increment) <= so.min)
   1585             {
   1586               value[0] = so.min;
   1587               value[1] = +1;
   1588             }
   1589           }
   1590 
   1591           std::memcpy(dst, value.data(), sizeof(value));
   1592         }
   1593         break;
   1594 
   1595         case SourceOptionType::MousePoint:
   1596         {
   1597           const std::pair<float, float> mpos = InputManager::GetPointerAbsolutePosition(0);
   1598           std::memcpy(dst, &mpos.first, sizeof(float));
   1599           std::memcpy(dst + sizeof(float), &mpos.second, sizeof(float));
   1600         }
   1601         break;
   1602 
   1603         case SourceOptionType::Random:
   1604         {
   1605           const s32 rv = m_random() % 32767; // reshade uses rand(), which on some platforms has a 0x7fff maximum.
   1606           std::memcpy(dst, &rv, sizeof(rv));
   1607         }
   1608         break;
   1609         case SourceOptionType::RandomF:
   1610         {
   1611           const float rv = (m_random() - m_random.min()) / static_cast<float>(m_random.max() - m_random.min());
   1612           std::memcpy(dst, &rv, sizeof(rv));
   1613         }
   1614         break;
   1615 
   1616         case SourceOptionType::BufferWidth:
   1617         case SourceOptionType::BufferHeight:
   1618         {
   1619           const s32 value = (so.source == SourceOptionType::BufferWidth) ? static_cast<s32>(target_width) :
   1620                                                                            static_cast<s32>(target_height);
   1621           std::memcpy(dst, &value, sizeof(value));
   1622         }
   1623         break;
   1624 
   1625         case SourceOptionType::BufferWidthF:
   1626         case SourceOptionType::BufferHeightF:
   1627         {
   1628           const float value = (so.source == SourceOptionType::BufferWidthF) ? static_cast<float>(target_width) :
   1629                                                                               static_cast<float>(target_height);
   1630           std::memcpy(dst, &value, sizeof(value));
   1631         }
   1632         break;
   1633 
   1634         case SourceOptionType::InternalWidth:
   1635         case SourceOptionType::InternalHeight:
   1636         {
   1637           const s32 value = (so.source == SourceOptionType::InternalWidth) ? static_cast<s32>(orig_width) :
   1638                                                                              static_cast<s32>(orig_height);
   1639           std::memcpy(dst, &value, sizeof(value));
   1640         }
   1641         break;
   1642 
   1643         case SourceOptionType::InternalWidthF:
   1644         case SourceOptionType::InternalHeightF:
   1645         {
   1646           const float value = (so.source == SourceOptionType::InternalWidthF) ? static_cast<float>(orig_width) :
   1647                                                                                 static_cast<float>(orig_height);
   1648           std::memcpy(dst, &value, sizeof(value));
   1649         }
   1650         break;
   1651 
   1652         case SourceOptionType::NativeWidth:
   1653         case SourceOptionType::NativeHeight:
   1654         {
   1655           const s32 value = (so.source == SourceOptionType::NativeWidth) ? static_cast<s32>(native_width) :
   1656                                                                            static_cast<s32>(native_height);
   1657           std::memcpy(dst, &value, sizeof(value));
   1658         }
   1659         break;
   1660 
   1661         case SourceOptionType::NativeWidthF:
   1662         case SourceOptionType::NativeHeightF:
   1663         {
   1664           const float value = (so.source == SourceOptionType::NativeWidthF) ? static_cast<float>(native_width) :
   1665                                                                               static_cast<float>(native_height);
   1666           std::memcpy(dst, &value, sizeof(value));
   1667         }
   1668         break;
   1669 
   1670         case SourceOptionType::UpscaleMultiplier:
   1671         {
   1672           const float value = static_cast<float>(orig_width) / static_cast<float>(native_width);
   1673           std::memcpy(dst, &value, sizeof(value));
   1674         }
   1675         break;
   1676 
   1677         case SourceOptionType::ViewportX:
   1678         {
   1679           const float value = static_cast<float>(final_rect.left);
   1680           std::memcpy(dst, &value, sizeof(value));
   1681         }
   1682         break;
   1683 
   1684         case SourceOptionType::ViewportY:
   1685         {
   1686           const float value = static_cast<float>(final_rect.top);
   1687           std::memcpy(dst, &value, sizeof(value));
   1688         }
   1689         break;
   1690 
   1691         case SourceOptionType::ViewportWidth:
   1692         {
   1693           const float value = static_cast<float>(final_rect.width());
   1694           std::memcpy(dst, &value, sizeof(value));
   1695         }
   1696         break;
   1697 
   1698         case SourceOptionType::ViewportHeight:
   1699         {
   1700           const float value = static_cast<float>(final_rect.height());
   1701           std::memcpy(dst, &value, sizeof(value));
   1702         }
   1703         break;
   1704 
   1705         case SourceOptionType::ViewportOffset:
   1706         {
   1707           GSVector4::storel(dst, GSVector4(final_rect));
   1708         }
   1709         break;
   1710 
   1711         case SourceOptionType::ViewportSize:
   1712         {
   1713           const float value[2] = {static_cast<float>(final_rect.width()), static_cast<float>(final_rect.height())};
   1714           std::memcpy(dst, &value, sizeof(value));
   1715         }
   1716         break;
   1717 
   1718         case SourceOptionType::InternalPixelSize:
   1719         {
   1720           const float value[2] = {static_cast<float>(final_rect.width()) / static_cast<float>(orig_width),
   1721                                   static_cast<float>(final_rect.height()) / static_cast<float>(orig_height)};
   1722           std::memcpy(dst, value, sizeof(value));
   1723         }
   1724         break;
   1725 
   1726         case SourceOptionType::InternalNormPixelSize:
   1727         {
   1728           const float value[2] = {(static_cast<float>(final_rect.width()) / static_cast<float>(orig_width)) /
   1729                                     static_cast<float>(target_width),
   1730                                   (static_cast<float>(final_rect.height()) / static_cast<float>(orig_height)) /
   1731                                     static_cast<float>(target_height)};
   1732           std::memcpy(dst, value, sizeof(value));
   1733         }
   1734         break;
   1735 
   1736         case SourceOptionType::NativePixelSize:
   1737         {
   1738           const float value[2] = {static_cast<float>(final_rect.width()) / static_cast<float>(native_width),
   1739                                   static_cast<float>(final_rect.height()) / static_cast<float>(native_height)};
   1740           std::memcpy(dst, value, sizeof(value));
   1741         }
   1742         break;
   1743 
   1744         case SourceOptionType::NativeNormPixelSize:
   1745         {
   1746           const float value[2] = {(static_cast<float>(final_rect.width()) / static_cast<float>(native_width)) /
   1747                                     static_cast<float>(target_width),
   1748                                   (static_cast<float>(final_rect.height()) / static_cast<float>(native_height)) /
   1749                                     static_cast<float>(target_height)};
   1750           std::memcpy(dst, value, sizeof(value));
   1751         }
   1752         break;
   1753 
   1754         case SourceOptionType::BufferToViewportRatio:
   1755         {
   1756           const float value[2] = {static_cast<float>(target_width) / static_cast<float>(final_rect.width()),
   1757                                   static_cast<float>(target_height) / static_cast<float>(final_rect.height())};
   1758           std::memcpy(dst, value, sizeof(value));
   1759         }
   1760         break;
   1761 
   1762         default:
   1763           UnreachableCode();
   1764           break;
   1765       }
   1766     }
   1767     g_gpu_device->UnmapUniformBuffer(m_uniforms_size);
   1768   }
   1769 
   1770   for (const Pass& pass : m_passes)
   1771   {
   1772     GL_SCOPE_FMT("Draw pass {}", pass.name.c_str());
   1773     DebugAssert(!pass.render_targets.empty());
   1774 
   1775     // Sucks doing this twice, but we need to set the RT first (for DX11), and transition layouts (for VK).
   1776     for (const Sampler& sampler : pass.samplers)
   1777     {
   1778       GPUTexture* const tex = GetTextureByID(sampler.texture_id, input_color, input_depth, final_target);
   1779       if (tex)
   1780         tex->MakeReadyForSampling();
   1781     }
   1782 
   1783     if (pass.render_targets.size() == 1 && pass.render_targets[0] == OUTPUT_COLOR_TEXTURE && !final_target)
   1784     {
   1785       // Special case: drawing to final buffer.
   1786       if (!g_gpu_device->BeginPresent(false))
   1787       {
   1788         GL_POP();
   1789         return false;
   1790       }
   1791     }
   1792     else
   1793     {
   1794       std::array<GPUTexture*, GPUDevice::MAX_RENDER_TARGETS> render_targets;
   1795       for (size_t i = 0; i < pass.render_targets.size(); i++)
   1796       {
   1797         GL_INS_FMT("Render Target {}: ID {} [{}]", i, pass.render_targets[i],
   1798                    GetTextureNameForID(pass.render_targets[i]));
   1799         render_targets[i] = GetTextureByID(pass.render_targets[i], input_color, input_depth, final_target);
   1800         DebugAssert(render_targets[i]);
   1801       }
   1802 
   1803       g_gpu_device->SetRenderTargets(render_targets.data(), static_cast<u32>(pass.render_targets.size()), nullptr);
   1804     }
   1805 
   1806     g_gpu_device->SetPipeline(pass.pipeline.get());
   1807 
   1808     // Set all inputs first, before the render pass starts.
   1809     std::bitset<GPUDevice::MAX_TEXTURE_SAMPLERS> bound_textures = {};
   1810     for (const Sampler& sampler : pass.samplers)
   1811     {
   1812       // Can't bind the RT as a sampler.
   1813       if (std::any_of(pass.render_targets.begin(), pass.render_targets.end(),
   1814                       [&sampler](TextureID rt) { return rt == sampler.texture_id; }))
   1815       {
   1816         GL_INS_FMT("Not binding RT sampler {}: ID {} [{}]", sampler.slot, sampler.texture_id,
   1817                    GetTextureNameForID(sampler.texture_id));
   1818         continue;
   1819       }
   1820 
   1821       GL_INS_FMT("Texture Sampler {}: ID {} [{}]", sampler.slot, sampler.texture_id,
   1822                  GetTextureNameForID(sampler.texture_id));
   1823       g_gpu_device->SetTextureSampler(
   1824         sampler.slot, GetTextureByID(sampler.texture_id, input_color, input_depth, final_target), sampler.sampler);
   1825       bound_textures[sampler.slot] = true;
   1826     }
   1827 
   1828     // Ensure RT wasn't left bound as a previous output, it breaks VK/DX12.
   1829     // TODO: Maybe move this into the backend? Not sure...
   1830     for (u32 i = 0; i < GPUDevice::MAX_TEXTURE_SAMPLERS; i++)
   1831     {
   1832       if (!bound_textures[i])
   1833         g_gpu_device->SetTextureSampler(i, nullptr, nullptr);
   1834     }
   1835 
   1836     g_gpu_device->Draw(pass.num_vertices, 0);
   1837   }
   1838 
   1839   // Don't leave any textures bound.
   1840   for (u32 i = 0; i < GPUDevice::MAX_TEXTURE_SAMPLERS; i++)
   1841     g_gpu_device->SetTextureSampler(i, nullptr, nullptr);
   1842 
   1843   GL_POP();
   1844   m_frame_timer.Reset();
   1845   return true;
   1846 }