/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
 *  Copyright (C) 2011-2017 - Daniel De Matteis
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http:www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>

#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif

#include <libretro.h>
#include <compat/posix_string.h>
#include <compat/msvc.h>
#include <compat/strl.h>
#include <file/file_path.h>
#include <lrc_hash.h>
#include <string/stdstring.h>
#include <streams/file_stream.h>
#include <lists/dir_list.h>
#include <lists/string_list.h>

#ifdef HAVE_MENU
#include "../menu/menu_driver.h"
#include "../menu/menu_shader.h"
#endif

#include "../configuration.h"
#include "../verbosity.h"
#include "../frontend/frontend_driver.h"
#include "../command.h"
#include "../list_special.h"
#include "../file_path_special.h"
#include "../paths.h"
#include "../retroarch.h"

#if defined(HAVE_GFX_WIDGETS)
#include "gfx_widgets.h"
#endif

#include "video_shader_parse.h"

#if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS)
#include "drivers_shader/slang_process.h"
#endif

/* Maximum depth of chain of referenced shader presets.
 * 16 seems to be a very large number of references at the moment. */
#define SHADER_MAX_REFERENCE_DEPTH 16
#define SHADER_NUM_WILDCARDS 15

enum wildcard_type
{
   RARCH_WILDCARD_CONTENT_DIR = 0,
   RARCH_WILDCARD_CORE,
   RARCH_WILDCARD_GAME,
   RARCH_WILDCARD_VIDEO_DRIVER,
   RARCH_WILDCARD_VIDEO_USER_ROTATION,
   RARCH_WILDCARD_VIDEO_ALLOW_CORE_ROTATION,
   RARCH_WILDCARD_CORE_REQUESTED_ROTATION,
   RARCH_WILDCARD_VIDEO_FINAL_ROTATION,
   RARCH_WILDCARD_SCREEN_ORIENTATION,
   RARCH_WILDCARD_VIEWPORT_ASPECT_ORIENTATION,
   RARCH_WILDCARD_CORE_ASPECT_ORIENTATION,
   RARCH_WILDCARD_PRESET_DIR,
   RARCH_WILDCARD_PRESET,
   RARCH_WILDCARD_VIDEO_DRIVER_SHADER_EXT,
   RARCH_WILDCARD_VIDEO_DRIVER_PRESET_EXT
};

struct wildcard_token
{
   enum wildcard_type token_id;
   char token_name[24];
};

/* TODO/FIXME - global state - perhaps move outside this file */
static path_change_data_t *file_change_data = NULL;

/**
 * fill_pathname_expanded_and_absolute:
 * @param s
 * String to write into.
 * @param in_refpath
 * Used to get the base path if in_path is relative.
 * @param in_path
 * Path to turn into an absolute path.
 *
 * Takes a path and returns an absolute path.
 * It will expand it if the path was using the root path format.
 * e.g. :\shaders
 * If the path was relative it will take this path and get the
 * absolute path using in_refpath as the path to extract a base path.
 *
 * s is filled with the absolute path
 **/
static void fill_pathname_expanded_and_absolute(char *s, size_t len,
      const char *in_refpath, const char *in_path)
{
   /* Expand paths which start with :\ to an absolute path */
   fill_pathname_expand_special(s, in_path, len);
   /* Resolve the reference path relative to the config */
   if (!path_is_absolute(s))
      fill_pathname_resolve_relative(s, in_refpath,
            in_path, len);
   pathname_conform_slashes_to_os(s);
}

/**
 * video_shader_replace_wildcards:
 *
 * @param inout_absolute_path
 * Absolute path to replace wildcards in
 *
 * @param in_preset_path
 * Path of the preset that this path is being used in
 *
 * All the wildcards are replaced by the live values which come from retroarch
 * or the preset context
 *
 * Possible wildcards/tokens to be replaced:
 *
 *   $CONTENT-DIR$ -> Content Directory of the game rom
 *
 *   $CORE$       -> Core name
 *
 *   $GAME$       -> Game ROM's name
 *
 *   $VID-DRV$   -> Video Driver: Currently active driver, possible replacement values:
 *       glcore
 *       gl
 *       vulkan
 *       d3d11
 *       d3d9_hlsl
 *       "N/A"
 *
 *   $VID-DRV-SHADER-EXT$   -> Video Driver Shader File Extension: The extension of shaders type supported by the current video driver:
 *       cg
 *       glsl
 *       slang
 *
 *   $VID-DRV-PRESET-EXT$   -> Video Driver Preset File Extension: The extension of shaders type supported by the current video driver:
 *       cgp
 *       glslp
 *       slangp
 *
 *   $CORE-REQ-ROT$   -> Core Requested Rotation: Rotation the core is requesting, possible replacement values:
 *       CORE-REQ-ROT-0
 *       CORE-REQ-ROT-90
 *       CORE-REQ-ROT-180
 *       CORE-REQ-ROT-270
 *
 *   $VID-ALLOW-CORE-ROT$   -> Video Allow Core Rotation: Reflect's Retroarch's setting allowing the core requested rotation to affect the final rotation:
 *       VID-ALLOW-CORE-ROT-OFF
 *       VID-ALLOW-CORE-ROT-ON
 *
 *   $VID-USER-ROT$   -> Video User Rotation: Rotation the core is requesting, possible replacement values, does not affect the UI:
 *       VID-USER-ROT-0
 *       VID-USER-ROT-90
 *       VID-USER-ROT-180
 *       VID-USER-ROT-270
 *
 *   $VID-FINAL-ROT$   -> Video Final Rotation: Rotation which is the sum of the user rotation and the core rotation if it has been allowed, does not affect the UI:
 *       VID-FINAL-ROT-0
 *       VID-FINAL-ROT-90
 *       VID-FINAL-ROT-180
 *       VID-FINAL-ROT-270
 *
 *   $SCREEN-ORIENT$   -> Screen Orientation: User adjusted screen orientation, will change windows from landscape to portrait, including the Retroarch UI:
 *       SCREEN-ORIENT-0
 *       SCREEN-ORIENT-90
 *       SCREEN-ORIENT-180
 *       SCREEN-ORIENT-270
 *
 *   $VIEW-ASPECT-ORIENT$   -> Viewport Aspect Orientation: Orientation of the aspect ratio of the retroarch viewport
 *       VIEW-ASPECT-ORIENT-HORZ
 *       VIEW-ASPECT-ORIENT-VERT
 *
 *   $CORE-ASPECT-ORIENT$   -> Core Aspect Orientation: Orientation of the aspect ratio requested by the core
 *       CORE-ASPECT-ORIENT-HORZ
 *       CORE-ASPECT-ORIENT-VERT
 *
 *   $PRESET_DIR$  -> Preset directory's name
 *
 *   $PRESET$     -> Preset's name
 *
 * If no wildcards are found within the path, or the path
 * after replacing the wildcards does not exist on disk,
 * the path returned will be uneffected.
 **/
static void video_shader_replace_wildcards(char *s, size_t len, char *in_preset_path)
{
   int i = 0;
   char replaced_path[PATH_MAX_LENGTH];
   static struct wildcard_token wildcard_tokens[SHADER_NUM_WILDCARDS] = {
      {RARCH_WILDCARD_CONTENT_DIR,                 "$CONTENT-DIR$"},
      {RARCH_WILDCARD_CORE,                        "$CORE$"},
      {RARCH_WILDCARD_GAME,                        "$GAME$"},
      {RARCH_WILDCARD_VIDEO_DRIVER,                "$VID-DRV$"},
      {RARCH_WILDCARD_VIDEO_DRIVER_PRESET_EXT,     "$VID-DRV-PRESET-EXT$"},
      {RARCH_WILDCARD_VIDEO_DRIVER_SHADER_EXT,     "$VID-DRV-SHADER-EXT$"},
      {RARCH_WILDCARD_CORE_REQUESTED_ROTATION,     "$CORE-REQ-ROT$"},
      {RARCH_WILDCARD_VIDEO_ALLOW_CORE_ROTATION,   "$VID-ALLOW-CORE-ROT$"},
      {RARCH_WILDCARD_VIDEO_USER_ROTATION,         "$VID-USER-ROT$"},
      {RARCH_WILDCARD_VIDEO_FINAL_ROTATION,        "$VID-FINAL-ROT$"},
      {RARCH_WILDCARD_SCREEN_ORIENTATION,          "$SCREEN-ORIENT$"},
      {RARCH_WILDCARD_VIEWPORT_ASPECT_ORIENTATION, "$VIEW-ASPECT-ORIENT$"},
      {RARCH_WILDCARD_CORE_ASPECT_ORIENTATION,     "$CORE-ASPECT-ORIENT$"},
      {RARCH_WILDCARD_PRESET_DIR,                  "$PRESET-DIR$"},
      {RARCH_WILDCARD_PRESET,                      "$PRESET$"},
   };

   if (!strstr(s, RARCH_WILDCARD_DELIMITER))
      return;

   strlcpy(replaced_path, s, sizeof(replaced_path));

   /* Step through the wildcards while we can still find the
    * delimiter in the replaced path
   */
   for (i = 0; (i < SHADER_NUM_WILDCARDS) && (strstr(replaced_path, RARCH_WILDCARD_DELIMITER)); i++)
   {
      /* If the wildcard text is in the path then process it */
      if (strstr(replaced_path, wildcard_tokens[i].token_name))
      {
         size_t _len = 0;
         char replace_text[PATH_MAX_LENGTH];

         switch (wildcard_tokens[i].token_id)
         {
            case RARCH_WILDCARD_CONTENT_DIR:
               {
                  char content_dir_name[DIR_MAX_LENGTH] = "";
                  const char* rarch_path_basename = path_get(RARCH_PATH_BASENAME);
                  if (rarch_path_basename)
                     fill_pathname_parent_dir_name(content_dir_name,
                           rarch_path_basename,
                           sizeof(content_dir_name));
                  if (memcmp(content_dir_name, "", sizeof("")) != 0)
                     strlcpy(content_dir_name,
                           path_basename_nocompression(content_dir_name),
                           sizeof(content_dir_name));
                  if (memcmp(content_dir_name, "", sizeof("")) != 0)
                     path_remove_extension(content_dir_name);

                  if (memcmp(content_dir_name, "", sizeof("")) != 0)
                     _len = strlcpy(replace_text, content_dir_name, sizeof(replace_text));
                  else
                     replace_text[0] = '\0';
               }
               break;
            case RARCH_WILDCARD_CORE:
               _len = strlcpy(replace_text, runloop_state_get_ptr()->system.info.library_name, sizeof(replace_text));
               break;
            case RARCH_WILDCARD_GAME:
               {
                  const char* path_basename = path_get(RARCH_PATH_BASENAME);
                  if (path_basename)
                     path_basename = path_basename_nocompression(path_basename);
                  if (path_basename)
                     _len = strlcpy(replace_text, path_basename, sizeof(replace_text));
                  else
                     replace_text[0] = '\0';
               }
               break;
            case RARCH_WILDCARD_VIDEO_DRIVER:
               _len = strlcpy(replace_text, config_get_ptr()->arrays.video_driver, sizeof(replace_text));
               break;
            case RARCH_WILDCARD_CORE_REQUESTED_ROTATION:
               _len  = strlcpy(replace_text, "CORE-REQ-ROT-", sizeof(replace_text));
               _len += snprintf(
                     replace_text         + _len,
                     sizeof(replace_text) - _len,
                     "%d",
                     retroarch_get_core_requested_rotation() * 90);
               break;
            case RARCH_WILDCARD_VIDEO_ALLOW_CORE_ROTATION:
               _len = strlcpy(replace_text, "VID-ALLOW-CORE-ROT-O",
                        sizeof(replace_text));
               if (config_get_ptr()->bools.video_allow_rotate)
                  _len += strlcpy(replace_text + _len, "N", sizeof(replace_text) - _len);
               else
                  _len += strlcpy(replace_text + _len, "FF", sizeof(replace_text) - _len);
               break;
            case RARCH_WILDCARD_VIDEO_USER_ROTATION:
               _len  = strlcpy(replace_text, "VID-USER-ROT-", sizeof(replace_text));
               _len += snprintf(
                     replace_text         + _len,
                     sizeof(replace_text) - _len,
                     "%d",
                     config_get_ptr()->uints.video_rotation * 90);
               break;
            case RARCH_WILDCARD_VIDEO_FINAL_ROTATION:
               _len  = strlcpy(replace_text, "VID-FINAL-ROT-", sizeof(replace_text));
               _len += snprintf(
                     replace_text         + _len,
                     sizeof(replace_text) - _len,
                     "%d",
                     retroarch_get_rotation() * 90);
               break;
            case RARCH_WILDCARD_SCREEN_ORIENTATION:
               _len  = strlcpy(replace_text, "SCREEN-ORIENT-", sizeof(replace_text));
               _len += snprintf(
                     replace_text         + _len,
                     sizeof(replace_text) - _len,
                     "%d",
                     config_get_ptr()->uints.screen_orientation * 90);
               break;
            case RARCH_WILDCARD_CORE_ASPECT_ORIENTATION:
               {
                  const int requested_rotation = retroarch_get_core_requested_rotation();
                  _len = strlcpy(replace_text,
                        (video_driver_get_core_aspect() < 1 || requested_rotation == 1 || requested_rotation == 3)
                        ? "CORE-ASPECT-ORIENT-VERT" : "CORE-ASPECT-ORIENT-HORZ",
                        sizeof(replace_text));
               }
               break;
            case RARCH_WILDCARD_VIEWPORT_ASPECT_ORIENTATION:
               {
                  unsigned viewport_width  = 0;
                  unsigned viewport_height = 0;
                  video_driver_get_size(&viewport_width, &viewport_height);
                  _len = strlcpy(replace_text,
                        ((float)viewport_width / viewport_height < 1)
                        ? "VIEW-ASPECT-ORIENT-VERT"
                        : "VIEW-ASPECT-ORIENT-HORZ",
                        sizeof(replace_text));
               }
               break;
            case RARCH_WILDCARD_PRESET_DIR:
               {
                  char preset_dir_name[DIR_MAX_LENGTH];
                  fill_pathname_parent_dir_name(preset_dir_name,
                        in_preset_path, sizeof(preset_dir_name));
                  if (memcmp(preset_dir_name, "", sizeof("")) != 0)
                     strlcpy(preset_dir_name,
                           path_basename_nocompression(preset_dir_name),
                           sizeof(preset_dir_name));
                  if (memcmp(preset_dir_name, "", sizeof("")) != 0)
                     path_remove_extension(preset_dir_name);
                  if (memcmp(preset_dir_name, "", sizeof("")) != 0)
                     _len = strlcpy(replace_text,
                           preset_dir_name, sizeof(replace_text));
                  else
                     replace_text[0] = '\0';
               }
               break;
            case RARCH_WILDCARD_PRESET:
               {
                  char preset_name[NAME_MAX_LENGTH];
                  strlcpy(preset_name,
                        path_basename_nocompression(in_preset_path),
                        sizeof(preset_name));
                  if (memcmp(preset_name, "", sizeof("")) != 0)
                     path_remove_extension(preset_name);
                  if (memcmp(preset_name, "", sizeof("")) != 0)
                     _len = strlcpy(replace_text,
                           preset_name, sizeof(replace_text));
                  else
                     replace_text[0] = '\0';
               }
               break;
            case RARCH_WILDCARD_VIDEO_DRIVER_SHADER_EXT:
               {
                  gfx_ctx_flags_t flags;
                  flags.flags = 0;
                  video_context_driver_get_flags(&flags);

                  if (BIT32_GET(flags.flags, GFX_CTX_FLAGS_SHADERS_CG))
                     _len = strlcpy(replace_text, "cg", sizeof(replace_text));
                  else if (BIT32_GET(flags.flags, GFX_CTX_FLAGS_SHADERS_GLSL))
                     _len = strlcpy(replace_text, "glsl", sizeof(replace_text));
                  else if (BIT32_GET(flags.flags, GFX_CTX_FLAGS_SHADERS_SLANG))
                     _len = strlcpy(replace_text, "slang", sizeof(replace_text));
                  else
                     replace_text[0] = '\0';
               }
               break;
            case RARCH_WILDCARD_VIDEO_DRIVER_PRESET_EXT:
               {
                  gfx_ctx_flags_t flags;
                  flags.flags = 0;
                  video_context_driver_get_flags(&flags);

                  if (BIT32_GET(flags.flags, GFX_CTX_FLAGS_SHADERS_CG))
                     _len = strlcpy(replace_text, "cgp", sizeof(replace_text));
                  else if (BIT32_GET(flags.flags, GFX_CTX_FLAGS_SHADERS_GLSL))
                     _len = strlcpy(replace_text, "glslp", sizeof(replace_text));
                  else if (BIT32_GET(flags.flags, GFX_CTX_FLAGS_SHADERS_SLANG))
                     _len = strlcpy(replace_text, "slangp", sizeof(replace_text));
                  else
                     replace_text[0] = '\0';
               }
               break;
            default:
               replace_text[0] = '\0';
               break;
         }
         {
            char *replace_output = string_replace_substring(replaced_path,
               sizeof(replaced_path),
               wildcard_tokens[i].token_name,
               strlen(wildcard_tokens[i].token_name),
               replace_text,
               _len);
            strlcpy(replaced_path, replace_output, sizeof(replaced_path));
            free(replace_output);
         }
      }
   }

   if (path_is_valid(replaced_path))
      strlcpy(s, replaced_path, len);
   else
   {
      /* If a file does not exist at the location of the replaced path
       * then output the original path instead */
      RARCH_DBG("[Shaders] Filepath after wildcard replacement can't be found:\n");
      RARCH_DBG("                \"%s\" \n", replaced_path);
      RARCH_DBG("           Falling back to original Filepath\n");
      RARCH_DBG("                \"%s\" \n\n", s);
   }
}

/**
 * video_shader_gather_reference_path_list:
 *
 * @param path_linked_list
 * List of paths which accrues as we move down the chain of references
 * @param path
 * Current path of preset to process
 * @param reference_depth
 * This is used to check against the SHADER_MAX_REFERENCE_DEPTH
 *
 * @return void
 **/
static void video_shader_gather_reference_path_list(
      struct path_linked_list *in_path_linked_list,
      char *path,
      int reference_depth)
{
   config_file_t *conf = NULL;

   if (reference_depth > SHADER_MAX_REFERENCE_DEPTH)
      return;

   if ((conf = config_file_new_from_path_to_string(path)))
   {
      struct path_linked_list *ref_tmp = (struct path_linked_list*)conf->references;
      while (ref_tmp)
      {
         char* reference_preset_path = (char*)malloc(PATH_MAX_LENGTH);
         /* Get the absolute path and replace wildcards in the path */
         fill_pathname_expanded_and_absolute(reference_preset_path, PATH_MAX_LENGTH, conf->path, ref_tmp->path);
         /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
         video_shader_replace_wildcards(reference_preset_path, PATH_MAX_LENGTH, conf->path);
         video_shader_gather_reference_path_list(in_path_linked_list, reference_preset_path, reference_depth + 1);
         free(reference_preset_path);
         ref_tmp = ref_tmp->next;
      }
      path_linked_list_add_path(in_path_linked_list, path);
   }
   else
   {
      RARCH_WARN("[Shaders] No preset located at \"%s\".\n", path);
   }
   config_file_free(conf);
}

/**
 * video_shader_wrap_mode_to_str:
 * @param type
 * Wrap type.
 *
 * Translates wrap mode to human-readable string identifier.
 *
 * @return human-readable string identifier of wrap mode.
 **/
static const char *video_shader_wrap_mode_to_str(enum gfx_wrap_type type)
{
   switch (type)
   {
      case RARCH_WRAP_BORDER:
         return "clamp_to_border";
      case RARCH_WRAP_EDGE:
         return "clamp_to_edge";
      case RARCH_WRAP_REPEAT:
         return "repeat";
      case RARCH_WRAP_MIRRORED_REPEAT:
         return "mirrored_repeat";
      default:
         break;
   }

   return "???";
}

/**
 * video_shader_wrap_str_to_mode:
 * @param type
 * Wrap type in human-readable string format.
 *
 * Translates wrap mode from human-readable string to enum mode value.
 *
 * @return enum mode value of wrap type.
 **/
static enum gfx_wrap_type video_shader_wrap_str_to_mode(const char *wrap_mode)
{
   if (string_is_equal(wrap_mode,      "clamp_to_border"))
      return RARCH_WRAP_BORDER;
   else if (string_is_equal(wrap_mode, "clamp_to_edge"))
      return RARCH_WRAP_EDGE;
   else if (string_is_equal(wrap_mode, "repeat"))
      return RARCH_WRAP_REPEAT;
   else if (string_is_equal(wrap_mode, "mirrored_repeat"))
      return RARCH_WRAP_MIRRORED_REPEAT;

   RARCH_WARN("[Shaders] Invalid wrapping type \"%s\". Valid ones are: \"clamp_to_border\" "
         "(default), \"clamp_to_edge\", \"repeat\" and \"mirrored_repeat\". Falling back to default.\n",
         wrap_mode);
   return RARCH_WRAP_DEFAULT;
}

/**
 * video_shader_parse_pass:
 * @param conf
 * Preset file to read from.
 * @param pass
 * Shader passes handle.
 * @param i
 * Index of shader pass.
 *
 * Parses shader pass from preset file.
 *
 * @return true if successful, otherwise false.
 **/
static bool video_shader_parse_pass(config_file_t *conf,
      struct video_shader_pass *pass, unsigned i)
{
   size_t _len;
   char shader_var[64];
   char scale_type[64];
   char scale_type_x[64];
   char scale_type_y[64];
   char formatted_num[12];
   char tmp_path[PATH_MAX_LENGTH];
   struct gfx_fbo_scale *scale          = NULL;
   bool tmp_bool                        = false;
   struct config_entry_list *entry      = NULL;

   scale_type[0]      = scale_type_x[0] =
   scale_type_y[0]                      = '\0';
   formatted_num[0]   = shader_var[0]   = '\0';

   snprintf(formatted_num, sizeof(formatted_num), "%u", i);

   /* Source */
   _len  = strlcpy(shader_var, "shader", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);

   if (config_get_path(conf, shader_var, tmp_path, sizeof(tmp_path)) == 0)
   {
      RARCH_ERR("[Shaders] Couldn't parse shader source \"%s\".\n", shader_var);
      return false;
   }

   /* Get the absolute path and replace wildcards in the path */
   fill_pathname_expanded_and_absolute(pass->source.path,
         PATH_MAX_LENGTH, conf->path, tmp_path);
   /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
   video_shader_replace_wildcards(pass->source.path,
         PATH_MAX_LENGTH, conf->path);

   /* Smooth */
   _len  = strlcpy(shader_var, "filter_linear", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num,   sizeof(shader_var) - _len);

   if (config_get_bool(conf, shader_var, &tmp_bool))
      pass->filter = tmp_bool ? RARCH_FILTER_LINEAR : RARCH_FILTER_NEAREST;
   else
      pass->filter = RARCH_FILTER_UNSPEC;

   /* Wrapping mode */
   _len  = strlcpy(shader_var, "wrap_mode", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   if ((entry = config_get_entry(conf, shader_var))
         && !string_is_empty(entry->value))
      pass->wrap = video_shader_wrap_str_to_mode(entry->value);
   entry = NULL;

   /* Frame count mod */
   _len  = strlcpy(shader_var, "frame_count_mod", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   if ((entry = config_get_entry(conf, shader_var))
         && !string_is_empty(entry->value))
      pass->frame_count_mod = (unsigned)strtoul(entry->value, NULL, 0);
   entry = NULL;

   /* FBO types and mipmapping */
   _len  = strlcpy(shader_var, "srgb_framebuffer", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   if (config_get_bool(conf, shader_var, &tmp_bool))
   {
      if (tmp_bool)
         pass->fbo.flags |=  FBO_SCALE_FLAG_SRGB_FBO;
      else
         pass->fbo.flags &= ~FBO_SCALE_FLAG_SRGB_FBO;
   }

   _len  = strlcpy(shader_var, "float_framebuffer", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   if (config_get_bool(conf, shader_var, &tmp_bool))
   {
      if (tmp_bool)
         pass->fbo.flags |=  FBO_SCALE_FLAG_FP_FBO;
      else
         pass->fbo.flags &= ~FBO_SCALE_FLAG_FP_FBO;
   }

   _len  = strlcpy(shader_var, "mipmap_input", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   if (config_get_bool(conf, shader_var, &tmp_bool))
      pass->mipmap = tmp_bool;

   _len  = strlcpy(shader_var, "alias", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   if (!config_get_array(conf, shader_var, pass->alias, sizeof(pass->alias)))
      *pass->alias = '\0';

   /* Scale */
   scale = &pass->fbo;
   _len  = strlcpy(shader_var, "scale_type", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   config_get_array(conf, shader_var, scale_type, sizeof(scale_type));

   _len  = strlcpy(shader_var, "scale_type_x", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   config_get_array(conf, shader_var, scale_type_x, sizeof(scale_type_x));

   _len  = strlcpy(shader_var, "scale_type_y", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
   config_get_array(conf, shader_var, scale_type_y, sizeof(scale_type_y));

   if (*scale_type)
   {
      strlcpy(scale_type_x, scale_type, sizeof(scale_type_x));
      strlcpy(scale_type_y, scale_type, sizeof(scale_type_y));
   }
   else if (!*scale_type_x && !*scale_type_y)
      return true;

   scale->flags  |= FBO_SCALE_FLAG_VALID;
   scale->type_x  = RARCH_SCALE_INPUT;
   scale->type_y  = RARCH_SCALE_INPUT;
   scale->scale_x = 1.0;
   scale->scale_y = 1.0;

   if (*scale_type_x)
   {
      if (string_is_equal(scale_type_x, "source"))
         scale->type_x = RARCH_SCALE_INPUT;
      else if (string_is_equal(scale_type_x, "viewport"))
         scale->type_x = RARCH_SCALE_VIEWPORT;
      else if (string_is_equal(scale_type_x, "absolute"))
         scale->type_x = RARCH_SCALE_ABSOLUTE;
      else
      {
         RARCH_ERR("[Shaders] Invalid attribute: \"%s\".\n", scale_type_x);
         return false;
      }
   }

   if (*scale_type_y)
   {
      if (string_is_equal(scale_type_y, "source"))
         scale->type_y = RARCH_SCALE_INPUT;
      else if (string_is_equal(scale_type_y, "viewport"))
         scale->type_y = RARCH_SCALE_VIEWPORT;
      else if (string_is_equal(scale_type_y, "absolute"))
         scale->type_y = RARCH_SCALE_ABSOLUTE;
      else
      {
         RARCH_ERR("[Shaders] Invalid attribute: \"%s\".\n", scale_type_y);
         return false;
      }
   }

   _len = strlcpy(shader_var, "scale", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);

   if (scale->type_x == RARCH_SCALE_ABSOLUTE)
   {
      int iattr          = 0;
      if (config_get_int(conf, shader_var, &iattr))
         scale->abs_x    = iattr;
      else
      {
         _len = strlcpy(shader_var, "scale_x", sizeof(shader_var));
         strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
         if (config_get_int(conf, shader_var, &iattr))
            scale->abs_x = iattr;
      }
   }
   else
   {
      float fattr          = 0.0f;
      if (config_get_float(conf, shader_var, &fattr))
         scale->scale_x    = fattr;
      else
      {
	      _len = strlcpy(shader_var, "scale_x", sizeof(shader_var));
	      strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
         if (config_get_float(conf, shader_var, &fattr))
            scale->scale_x = fattr;
      }
   }

   _len = strlcpy(shader_var, "scale", sizeof(shader_var));
   strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);

   if (scale->type_y == RARCH_SCALE_ABSOLUTE)
   {
      int iattr          = 0;
      if (config_get_int(conf, shader_var, &iattr))
         scale->abs_y    = iattr;
      else
      {
         _len = strlcpy(shader_var, "scale_y", sizeof(shader_var));
         strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
         if (config_get_int(conf, shader_var, &iattr))
            scale->abs_y = iattr;
      }
   }
   else
   {
      float fattr          = 0.0f;
      if (config_get_float(conf, shader_var, &fattr))
         scale->scale_y    = fattr;
      else
      {
         _len = strlcpy(shader_var, "scale_y", sizeof(shader_var));
         strlcpy(shader_var + _len, formatted_num, sizeof(shader_var) - _len);
         if (config_get_float(conf, shader_var, &fattr))
            scale->scale_y = fattr;
      }
   }

   return true;
}

/**
 * video_shader_parse_textures:
 * @param conf
 * Preset file to read from.
 * @param shader
 * Shader pass handle.
 *
 * Parses shader textures.
 *
 * @return true if successful, otherwise false.
 **/
static bool video_shader_parse_textures(config_file_t *conf,
      struct video_shader *shader)
{
   char *textures       = (char*)malloc(1024 + PATH_MAX_LENGTH);

   if (!textures)
      return false;

   textures[0] = '\0';

   if (config_get_array(conf, "textures", textures, 1024))
   {
      char texture_path[PATH_MAX_LENGTH];
      const char *id  = NULL;
      char *save      = NULL;

      texture_path[0] = '\0';

      for (id = strtok_r(textures, ";", &save);
            id && shader->luts < GFX_MAX_TEXTURES;
            shader->luts++, id = strtok_r(NULL, ";", &save))
      {
         size_t _len;
         char idx[64];
         bool mipmap                     = false;
         bool smooth                     = false;
         struct config_entry_list *entry = NULL;

         idx[0]                          = '\0';

         if ( !(entry = config_get_entry(conf, id))
             || string_is_empty(entry->value))
         {
            RARCH_ERR("[Shaders] Cannot find path to texture \"%s\".\n",
                  id);
            free(textures);
            return false;
         }

         config_get_path(conf, id, texture_path, sizeof(texture_path));

         /* Get the absolute path and replace wildcards in the path */
         fill_pathname_expanded_and_absolute(shader->lut[shader->luts].path,
               PATH_MAX_LENGTH, conf->path, texture_path);
         /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
         video_shader_replace_wildcards(shader->lut[shader->luts].path,
               PATH_MAX_LENGTH, conf->path);

         strlcpy(shader->lut[shader->luts].id, id,
               sizeof(shader->lut[shader->luts].id));

         _len = strlcpy(idx, id, sizeof(idx));

         strlcpy(idx + _len, "_linear", sizeof(idx) - _len);
         if (config_get_bool(conf, idx, &smooth))
            shader->lut[shader->luts].filter = smooth
               ? RARCH_FILTER_LINEAR
               : RARCH_FILTER_NEAREST;
         else
            shader->lut[shader->luts].filter = RARCH_FILTER_UNSPEC;

         strlcpy(idx + _len, "_mipmap", sizeof(idx) - _len);
         if (config_get_bool(conf, idx, &mipmap))
            shader->lut[shader->luts].mipmap = mipmap;
         else
            shader->lut[shader->luts].mipmap = false;

         strlcpy(idx + _len, "_wrap_mode", sizeof(idx) - _len);
         entry = NULL;
         if ((entry = config_get_entry(conf, idx))
               && !string_is_empty(entry->value))
            shader->lut[shader->luts].wrap = video_shader_wrap_str_to_mode(entry->value);
         entry = NULL;

      }
   }

   free(textures);
   return true;
}

/**
 * video_shader_parse_find_parameter:
 * @param params
 * Shader parameter handle.
 * @param num_params
 * Number of shader params in @params.
 * param id
 * Identifier to search for.
 *
 * Finds a shader parameter with identifier @id in @params..
 *
 * @return Handle to shader parameter if successful, otherwise NULL.
 **/
static struct video_shader_parameter *video_shader_parse_find_parameter(
      struct video_shader_parameter *params,
      unsigned num_params, const char *id)
{
   size_t i;

   for (i = 0; (unsigned)i < num_params; i++)
   {
      if (string_is_equal(params[i].id, id))
         return &params[i];
   }

   return NULL;
}

/**
 * video_shader_resolve_parameters:
 * @conf              : Preset file to read from.
 * @shader            : Shader passes handle.
 *
 * Resolves all shader parameters belonging to shaders
 * from the #pragma parameter lines in the shader for each pass.
 *
 **/
void video_shader_resolve_parameters(struct video_shader *shader)
{
   size_t i;

   shader->num_parameters = 0;

#ifdef DEBUG
   /* Find all parameters in our shaders. */
   RARCH_DBG("[Shaders] Finding parameters in shader passes (#pragma parameter)...\n");
#endif

#if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS)
   for (i = 0; i < shader->passes; i++)
   {
      const char *path          = shader->pass[i].source.path;
      if (string_is_empty(path) || !path_is_valid(path))
         continue;
      /* Now uses the same slang parsing for parameters since
       * it should be the same implementation, but supporting
       * #include directives */
      slang_preprocess_parse_parameters(path, shader);
   }
#else
   {
      struct video_shader_parameter *param = &shader->parameters[0];
      for (i = 0; i < shader->passes; i++)
      {
         const char *path             = shader->pass[i].source.path;
         uint8_t *buf                 = NULL;
         int64_t buf_len              = 0;

         if (string_is_empty(path) || !path_is_valid(path))
            continue;

         /* Read file contents */
         if (filestream_read_file(path, (void**)&buf, &buf_len))
         {
            size_t line_index         = 0;
            struct string_list lines  = {0};
            bool lines_inited         = false;

            /* Split into lines */
            if (buf_len > 0)
            {
               string_list_initialize(&lines);
               lines_inited = string_split_noalloc(&lines, (const char*)buf, "\n");
            }

            /* Buffer is no longer required - clean up */
            if ((void*)buf)
               free((void*)buf);

            if (!lines_inited)
               continue;

            /* Even though the pass is set in the loop too,
             * not all passes have parameters */
            param->pass = (int)i;

            while ((shader->num_parameters < ARRAY_SIZE(shader->parameters))
                  && (line_index < lines.size))
            {
               int ret;
               const char *line = lines.elems[line_index].data;
               line_index++;

               /* Check if this is a '#pragma parameter' line */
               if (strncmp("#pragma parameter", line,
                        STRLEN_CONST("#pragma parameter")))
                  continue;

               /* Parse line */
               if ((ret = sscanf(line, "#pragma parameter %63s \"%63[^\"]\" %f %f %f %f",
                           param->id,        param->desc,    &param->initial,
                           &param->minimum, &param->maximum, &param->step)) < 5)
                  continue;

               param->id[63]   = '\0';
               param->desc[63] = '\0';

               if (ret == 5)
                  param->step  = 0.1f * (param->maximum - param->minimum);

               param->pass     = (int)i;

#ifdef DEBUG
               RARCH_DBG("[Shaders] Found #pragma parameter %s (%s) %f %f %f %f in pass %d.\n",
                     param->desc,    param->id,      param->initial,
                     param->minimum, param->maximum, param->step, param->pass);
#endif
               param->current  = param->initial;

               shader->num_parameters++;
               param++;
            }

            string_list_deinitialize(&lines);
         }
      }
   }
#endif
}


/**
 * video_shader_load_current_parameter_values:
 * @param conf
 * Preset file to read from.
 * @param shader
 * Shader passes handle.
 *
 * For each parameter in the shader, if a value is set in the config file
 * load this value to the parameter's current value.
 *
 * @return true (1) if successful, otherwise false (0).
 **/
bool video_shader_load_current_parameter_values(
      config_file_t *conf, struct video_shader *shader)
{
   size_t i;

   if (!conf)
      return false;

   /* For all parameters in the shader see if there is any config value set */
   for (i = 0; i < shader->num_parameters; i++)
   {
      const struct config_entry_list *entry = config_get_entry(conf, shader->parameters[i].id);

      /* Only try to load the parameter value if an entry exists in the config */
      if (entry)
      {
         struct video_shader_parameter *parameter = (struct video_shader_parameter*)
            video_shader_parse_find_parameter(shader->parameters,
                  shader->num_parameters,
                  shader->parameters[i].id);

         /* Log each parameter read */
         if (config_get_float(conf, shader->parameters[i].id, &parameter->current))
            RARCH_DBG("[Shaders] Load parameter value: \"%s\" = %f.\n", shader->parameters[i].id, parameter->current);
         else
            RARCH_WARN("[Shaders] Load parameter value: \"%s\" is set in preset but couldn't load its value.\n",
                        shader->parameters[i].id);
      }
   }

   return true;
}

static const char *video_shader_scale_type_to_str(enum gfx_scale_type type)
{
   switch (type)
   {
      case RARCH_SCALE_INPUT:
         return "source";
      case RARCH_SCALE_VIEWPORT:
         return "viewport";
      case RARCH_SCALE_ABSOLUTE:
         return "absolute";
      default:
         break;
   }

   return "?";
}

static void video_shader_write_scale_dim(config_file_t *conf,
      const char *dim,
      const char *formatted_num,
      enum gfx_scale_type type,
      float scale,
      unsigned absolute)
{
   char key[64];
   char dim_str[64];
   size_t _len = strlcpy(dim_str, dim, sizeof(dim_str));
   strlcpy(dim_str + _len, formatted_num, sizeof(dim_str) - _len);

   _len        = strlcpy(key, "scale_type_", sizeof(key));
   strlcpy(key + _len, dim_str, sizeof(key) - _len);
   config_set_string(conf, key, video_shader_scale_type_to_str(type));

   _len        = strlcpy(key, "scale_", sizeof(key));
   strlcpy(key + _len, dim_str,  sizeof(key) - _len);
   if (type == RARCH_SCALE_ABSOLUTE)
      config_set_int(conf, key, absolute);
   else
      config_set_float(conf, key, scale);
}

static void video_shader_write_fbo(config_file_t *conf,
      const char *formatted_num,
      const struct gfx_fbo_scale *fbo)
{
   char key[64];
   size_t _len = strlcpy(key, "float_framebuffer", sizeof(key));
   strlcpy(key + _len, formatted_num, sizeof(key) - _len);
   config_set_string(conf, key, (fbo->flags & FBO_SCALE_FLAG_FP_FBO) ? "true" : "false");
   _len = strlcpy(key, "srgb_framebuffer", sizeof(key));
   strlcpy(key + _len, formatted_num, sizeof(key) - _len);
   config_set_string(conf, key, (fbo->flags & FBO_SCALE_FLAG_SRGB_FBO) ? "true" : "false");

   if (fbo->flags & FBO_SCALE_FLAG_VALID)
   {
      video_shader_write_scale_dim(conf, "x", formatted_num, fbo->type_x, fbo->scale_x, fbo->abs_x);
      video_shader_write_scale_dim(conf, "y", formatted_num, fbo->type_y, fbo->scale_y, fbo->abs_y);
   }
}

/**
 * video_shader_write_root_preset:
 * @conf              : Preset file to write to.
 * @shader            : Shader passes handle.
 * @preset_path       : Optional path to where the preset will be written.
 *
 * Writes preset and all associated state (passes, textures, imports,
 * etc) into @conf.
 * If @preset_path is not NULL, shader paths are saved relative to it.
 **/
static bool video_shader_write_root_preset(const struct video_shader *shader,
      const char *path)
{
   size_t i;
   char key[64];
   bool ret             = true;
   char *tmp            = NULL;
   char *tmp_rel        = NULL;
   char *tmp_base       = NULL;
   config_file_t *conf  = config_file_new_alloc();

   if (!conf)
      return false;

   tmp = (char*)malloc(3 * PATH_MAX_LENGTH);

   if (!tmp)
   {
      config_file_free(conf);
      return false;
   }

   tmp_rel        = tmp +     PATH_MAX_LENGTH;
   tmp_base       = tmp + 2 * PATH_MAX_LENGTH;

   RARCH_DBG("[Shaders] Saving full preset to: \"%s\".\n", path);

   config_set_int(conf, "shaders", shader->passes);
   if (shader->feedback_pass >= 0)
      config_set_int(conf, "feedback_pass", shader->feedback_pass);

   strlcpy(tmp_base, path, PATH_MAX_LENGTH);

   /* Ensure we use a clean base like the shader passes and texture paths do */
   path_resolve_realpath(tmp_base, PATH_MAX_LENGTH, false);
   path_basedir(tmp_base);

   for (i = 0; i < shader->passes; i++)
   {
      size_t _len;
      char formatted_num[8];
      const struct video_shader_pass *pass = &shader->pass[i];

      formatted_num[0]                     = '\0';

      snprintf(formatted_num, sizeof(formatted_num), "%u", (int)i);

      _len = strlcpy(key, "shader", sizeof(key));
      strlcpy(key + _len, formatted_num, sizeof(key) - _len);

      strlcpy(tmp, pass->source.path, PATH_MAX_LENGTH);
      path_relative_to(tmp_rel, tmp, tmp_base, PATH_MAX_LENGTH);

      pathname_make_slashes_portable(tmp_rel);

      config_set_path(conf, key, tmp_rel);

      _len = strlcpy(key, "alias", sizeof(key));
      strlcpy(key + _len, formatted_num, sizeof(key) - _len);
      config_set_string(conf, key, pass->alias);

      _len = strlcpy(key, "wrap_mode",   sizeof(key));
      strlcpy(key + _len, formatted_num, sizeof(key) - _len);
      config_set_string(conf, key, video_shader_wrap_mode_to_str(pass->wrap));

      _len = strlcpy(key, "mipmap_input", sizeof(key));
      strlcpy(key + _len, formatted_num, sizeof(key) - _len);
      config_set_string(conf, key, pass->mipmap ? "true" : "false");

      if (pass->filter != RARCH_FILTER_UNSPEC)
      {
         _len = strlcpy(key, "filter_linear", sizeof(key));
         strlcpy(key + _len, formatted_num, sizeof(key) - _len);
         config_set_string(conf, key,
               (pass->filter == RARCH_FILTER_LINEAR)
               ? "true"
               : "false");
      }

      if (pass->frame_count_mod)
      {
         _len = strlcpy(key, "frame_count_mod", sizeof(key));
         strlcpy(key + _len, formatted_num, sizeof(key) - _len);
         config_set_int(conf, key, pass->frame_count_mod);
      }

      video_shader_write_fbo(conf, formatted_num, &pass->fbo);
   }

   /* Write shader parameters which are different than the default shader values */
   if (shader->num_parameters)
      for (i = 0; i < shader->num_parameters; i++)
         if (shader->parameters[i].current != shader->parameters[i].initial)
            config_set_float(conf, shader->parameters[i].id, shader->parameters[i].current);

   if (shader->luts)
   {
      char textures[4096]; /* TODO/FIXME - check size */
      /* Names of the textures */
      size_t _len = strlcpy(textures, shader->lut[0].id, sizeof(textures));

      for (i = 1; i < shader->luts; i++)
      {
         /* O(n^2), but number of textures is very limited. */
         _len += strlcpy(textures + _len, ";",               sizeof(textures) - _len);
         _len += strlcpy(textures + _len, shader->lut[i].id, sizeof(textures) - _len);
      }

      config_set_string(conf, "textures", textures);

      /* Step through the textures in the shader */
      for (i = 0; i < shader->luts; i++)
      {
         size_t _len;
         char k[128];
         fill_pathname_abbreviated_or_relative(tmp_rel,
               tmp_base, shader->lut[i].path, PATH_MAX_LENGTH);
         pathname_make_slashes_portable(tmp_rel);
         config_set_string(conf, shader->lut[i].id, tmp_rel);

         _len = strlcpy(k, shader->lut[i].id, sizeof(k));

         /* Mipmap On or Off */
         strlcpy(k + _len, "_mipmap", sizeof(k) - _len);
         config_set_string(conf, k, shader->lut[i].mipmap
               ? "true"
               : "false");

         /* Linear filter ON or OFF */
         if (shader->lut[i].filter != RARCH_FILTER_UNSPEC)
         {
            strlcpy(k + _len, "_linear", sizeof(k) - _len);
            config_set_string(conf, k,
                  (shader->lut[i].filter == RARCH_FILTER_LINEAR)
                  ? "true"
                  : "false");
         }

         /* Wrap Mode */
         strlcpy(k + _len, "_wrap_mode", sizeof(k) - _len);
         config_set_string(conf, k,
               video_shader_wrap_mode_to_str(shader->lut[i].wrap));
      }
   }

   /* Write the File! */
   ret = config_file_write(conf, path, false);

   config_file_free(conf);
   free(tmp);

   return ret;
}

static config_file_t *video_shader_get_root_preset_config(const char *path)
{
   int reference_depth           = 1;
   char* nested_reference_path   = NULL;
   config_file_t *conf           = config_file_new_from_path_to_string(path);

   if (!conf)
      return NULL;

   nested_reference_path         = (char*)malloc(PATH_MAX_LENGTH);

   while (conf->references)
   {
      /* If we have reached the max depth of nested references,
       * stop attempting to read the next reference,
       * because we are likely in a self referential loop.
       *
       * SHADER_MAX_REFERENCE_DEPTH references deep seems
       * like more than enough depth for expected usage */
      if (reference_depth > SHADER_MAX_REFERENCE_DEPTH)
      {
         RARCH_ERR("[Shaders] Get root preset - Exceeded maximum reference depth (%u) without finding a full preset. "
               "This chain of referenced presets is likely cyclical.\n", SHADER_MAX_REFERENCE_DEPTH);
         config_file_free(conf);
         conf = NULL;
         free(nested_reference_path);
         return NULL;
      }

      /* Get the absolute path and replace wildcards in the path */
      fill_pathname_expanded_and_absolute(nested_reference_path,
            PATH_MAX_LENGTH, conf->path, conf->references->path);
      /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
      video_shader_replace_wildcards(nested_reference_path,
            PATH_MAX_LENGTH, conf->path);

      /* Create a new config from the referenced path */
      config_file_free(conf);

      /* If we can't read the reference preset */
      if (!(conf = config_file_new_from_path_to_string(nested_reference_path)))
      {
         RARCH_WARN("[Shaders] Could not read shader preset in #reference line: \"%s\".\n", nested_reference_path);
         free(nested_reference_path);
         return NULL;
      }

      reference_depth += 1;
   }

   free(nested_reference_path);

   return conf;
}

/**
 * video_shader_check_reference_chain:
 * @param path_to_save
 * Path of the preset we want to validate is safe to save as
 * a simple preset.
 * @param reference_path
 * Path of the reference which we would want to write into
 * the new preset.
 *
 * Checks to see if we can save a valid simple preset
 * (preset with a #reference in it) to this path.
 *
 * This takes into account reference links which can't be
 * loaded and if saving this file would create a creating
 * circular reference chain, because some link in
 * the chain references the file path we want to save to.
 *
 * Checks each preset in the chain of presets with #reference
 * Starts with reference_path. If it has no reference, then
 * our check is valid.
 * If it has a #reference, then check that the reference path
 * is not the same as path_to_save.

 * If it is not the same path, then go the the next nested reference
 *
 * Continues this until it finds a preset without #reference in it,
 * or it hits the maximum recursion depth (at that point
 * it is probably in a self referential cycle)
 *
 * @return true (1) if it was able to load all presets and
 * found a full preset, otherwise false (0).
 **/
static bool video_shader_check_reference_chain_for_save(
      const char *path_to_save, const char *ref_path)
{
   config_file_t *conf    = config_file_new_from_path_to_string(ref_path);
   bool ret               = true;

   if (!conf)
   {
      RARCH_ERR("[Shaders] Could not read the #reference preset: \"%s\".\n", ref_path);
      return false;
   }
   else
   {
      int ref_depth                 = 1;
      char *path_to_save_conformed  = (char*)malloc(PATH_MAX_LENGTH);
      char *nested_ref_path         = (char*)malloc(PATH_MAX_LENGTH);
      strlcpy(path_to_save_conformed, path_to_save, PATH_MAX_LENGTH);
      pathname_conform_slashes_to_os(path_to_save_conformed);

      while (conf->references)
      {
         /* If we have reached the max depth of nested references stop attempting to read
          * the next reference because we are likely in a self referential loop. */
         if (ref_depth > SHADER_MAX_REFERENCE_DEPTH)
         {
            RARCH_ERR("[Shaders] Check reference chain for save - Exceeded maximum reference depth(%u) without "
                      "finding a full preset. This chain of referenced presets is likely cyclical.\n", SHADER_MAX_REFERENCE_DEPTH);
            ret = false;
            break;
         }

         /* Get the absolute path and replace wildcards in the path */
         fill_pathname_expanded_and_absolute(nested_ref_path,
               PATH_MAX_LENGTH, conf->path, conf->references->path);
         /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
         video_shader_replace_wildcards(nested_ref_path,
               PATH_MAX_LENGTH, conf->path);

         /* If one of the reference paths is the same as the file we want to save,
          * then this reference chain would be self-referential / cyclical and
          * we can't save this as a simple preset */
         if (string_is_equal(nested_ref_path, path_to_save_conformed))
         {
            RARCH_WARN("[Shaders] Saving preset:\n"
                       "        \"%s\"\n"
                       "        With a #reference of:\n"
                       "        \"%s\"\n"
                       "        Would create a cyclical reference in preset:\n"
                       "        \"%s\"\n"
                       "        Which already references preset:\n"
                       "        \"%s\"\n",
                       path_to_save_conformed, ref_path,
                       conf->path, nested_ref_path);
            ret = false;
            break;
         }

         /* Create a new config from the referenced path */
         config_file_free(conf);

         /* If we can't read the reference preset */
         if (!(conf = config_file_new_from_path_to_string(nested_ref_path)))
         {
            RARCH_WARN("[Shaders] Could not read shader preset "
                  "in #reference line: \"%s\".\n", nested_ref_path);
            ret = false;
            break;
         }

         ref_depth++;
      }

      free(path_to_save_conformed);
      free(nested_ref_path);
   }

   config_file_free(conf);

   return ret;
}

/**
 * video_shader_write_referenced_preset:
 * @param path
 * File to write to
 * @param shader
 * Shader preset to write
 *
 * Writes a referenced preset to disk
 * A referenced preset is a preset which includes the #reference directive
 * as it's first line to specify a root preset and can also
 * include parameter and texture values to override the values
 * of the root preset
 *
 * @return false if a referenced preset cannot be saved
 **/
static bool video_shader_write_referenced_preset(
      const struct video_shader *shader,
      const char *path_to_save)
{
   size_t i;
   char config_dir[DIR_MAX_LENGTH];
   config_file_t *conf                    = NULL;
   config_file_t *ref_conf                = NULL;
   struct video_shader *ref_shader        = (struct video_shader*)
      calloc(1, sizeof(*ref_shader));
   bool ret                               = false;
   char *path_to_ref                      = (char*)malloc(PATH_MAX_LENGTH);
   char* path_to_save_conformed           = (char*)malloc(PATH_MAX_LENGTH);

   strlcpy(path_to_save_conformed, path_to_save, PATH_MAX_LENGTH);
   pathname_conform_slashes_to_os(path_to_save_conformed);

   config_dir[0]                          = '\0';
   path_to_ref[0]                         = '\0';

   /* Get the retroarch config dir where the automatically
    * loaded presets are located
    * and where Save Game Preset, Save Core Preset,
    * Save Global Preset save to */
   fill_pathname_application_special(config_dir, DIR_MAX_LENGTH,
         APPLICATION_SPECIAL_DIRECTORY_CONFIG);

   /* If there is no initial preset path loaded */
   if (string_is_empty(shader->loaded_preset_path))
   {
      RARCH_WARN("[Shaders] Saving full preset because the loaded shader "
            "does not have "
            "a path to a previously loaded preset file on disk.\n");
      goto end;
   }

   /* If the initial preset loaded is the ever-changing retroarch
    * preset don't save a reference
    * TODO/FIXME - remove once we don't write this preset anymore */
   if (!strncmp(path_basename_nocompression(shader->loaded_preset_path),
            "retroarch",
            STRLEN_CONST("retroarch")))
   {
      RARCH_WARN("[Shaders] Saving full preset because "
            "a reference to the "
            "ever-changing retroarch preset can't be saved.\n");
      goto end;
   }

   strlcpy(path_to_ref, shader->loaded_preset_path, PATH_MAX_LENGTH);
   pathname_conform_slashes_to_os(path_to_ref);

   /* Get a config from the file we want to make a reference to */
   /* If the original preset can't be loaded, probably because
    * it isn't there anymore */
   if (!(ref_conf = config_file_new_from_path_to_string(path_to_ref)))
   {
      RARCH_WARN("[Shaders] Saving full preset because the initially "
            "loaded preset can't be loaded. "
            "It was likely renamed or deleted.\n");
      goto end;
   }

   /* If we are trying to save on top the path referenced in the
    * initially loaded preset.
    *
    * E.G. Preset_B references Preset_A, I load Preset_B do some
    * parameter adjustments,
    * then I save on top of Preset_A, we want to get a preset
    * just like the original Preset_A with the new parameter
    * adjustments.
    *
    * If there is a reference in the initially loaded preset,
    * we should check it against the preset path we are currently
    * trying to save */
   if (ref_conf->references)
   {
      char *abs_tmp_ref_path = (char*)malloc(PATH_MAX_LENGTH);
      abs_tmp_ref_path[0]    = '\0';
      /* Get the absolute path and replace wildcards in the path */
      fill_pathname_expanded_and_absolute(abs_tmp_ref_path, PATH_MAX_LENGTH,
            ref_conf->path, ref_conf->references->path);
      /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
      video_shader_replace_wildcards(abs_tmp_ref_path,
            PATH_MAX_LENGTH, ref_conf->path);
      pathname_conform_slashes_to_os(abs_tmp_ref_path);

      /* If the reference is the same as the path we are trying to save to
         then this should be used as the reference to save */
      if (string_is_equal(abs_tmp_ref_path, path_to_save_conformed))
      {
         strlcpy(path_to_ref, abs_tmp_ref_path, PATH_MAX_LENGTH);
         config_file_free(ref_conf);
         ref_conf = config_file_new_from_path_to_string(
               path_to_ref);
      }
      free(abs_tmp_ref_path);
   }

   /*
    * If
    *    The new preset file we are trying to save is the
    *    same as the initially loaded preset
    * or
    *    The initially loaded preset was located under the
    *    retroarch config folder
    *    this means that it was likely saved from inside the retroarch UI
    * Then
    *    We should not save a preset with a reference to the initially loaded
    *    preset file itself, instead we need to save a new preset with
    *    the same reference as was in the initially loaded preset.
    */

   /* If the reference path is the same as the path we want to save
    * or the reference path is in the config (auto shader) folder */
   if (      string_is_equal(path_to_ref, path_to_save_conformed)
         || !strncmp(config_dir, path_to_ref, strlen(config_dir)))
   {
      /* If the config from the reference path has a reference in it,
       * we will use this same nested reference for the new preset */
      if (ref_conf->references)
      {
         /* Get the absolute path and replace wildcards in the path */
         fill_pathname_expanded_and_absolute(path_to_ref, PATH_MAX_LENGTH,
               ref_conf->path, ref_conf->references->path);
         /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
         video_shader_replace_wildcards(path_to_ref,
               PATH_MAX_LENGTH, ref_conf->path);

         /* If the reference path is also the same as what
          * we are trying to save
            This can easily happen
            E.G.
            - Save Preset As
            - Save Game Preset
            - Save Preset As (use same name as first time)
         */
         if (string_is_equal(path_to_ref, path_to_save_conformed))
         {
            config_file_free(ref_conf);
            ref_conf = config_file_new_from_path_to_string(path_to_ref);

            /* If the reference also has a reference inside it */
            /* Get the absolute path for the reference */
            if (ref_conf->references)
            {
               /* Get the absolute path and replace wildcards in the path */
               fill_pathname_expanded_and_absolute(path_to_ref, PATH_MAX_LENGTH,
                     ref_conf->path, ref_conf->references->path);
               /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
               video_shader_replace_wildcards(path_to_ref,
                     PATH_MAX_LENGTH, ref_conf->path);
            }
            else
            {
               /* If the config referenced is a full preset */
               RARCH_WARN("[Shaders] Saving full preset because "
                     "a preset which "
                     "would reference itself can't be saved.\n");
               goto end;
            }
         }
      }
      /* If there is no reference in the initial preset we need to
       * save a full preset */
      else
      {
         /* We can't save a reference to ourselves */
         RARCH_WARN("[Shaders] Saving full preset because "
               "a preset which "
               "would reference itself can't be saved.\n");
         goto end;
      }
   }

   /* Check the reference chain that we would be saving to make sure it
    * is valid */
   if (!video_shader_check_reference_chain_for_save(
            path_to_save_conformed, path_to_ref))
   {
      RARCH_WARN("[Shaders] Saving full preset because saving a "
            "simple preset would result "
            "in a cyclical reference, or a preset in the reference "
            "chain could not be read.\n");
      goto end;
   }

   RARCH_DBG("[Shaders] Reading preset to compare with "
         "current values: \"%s\".\n", path_to_save_conformed);

   /* Load the preset referenced in the preset into the shader */
   if (!video_shader_load_preset_into_shader(path_to_ref, ref_shader))
   {
      RARCH_WARN("[Shaders] Saving full preset because "
            "the preset could not be loaded from #reference line: \"%s\".\n",
            path_to_ref);
      goto end;
   }

   /* Create a new EMPTY config */
   if (!(conf = config_file_new_alloc()))
      goto end;

   conf->path = strdup(path_to_save_conformed);

   /* Add the reference path to the config */
   config_file_add_reference(conf, path_to_ref);

   /* Set modified flag to true so when
    * you run config_file_write it will save a file */
   conf->flags |= CONF_FILE_FLG_MODIFIED;

   /*
      Compare the shader to a shader created from the referenced
      config to see if we can save a referenced preset and what
      parameters and textures of the root_config are overridden
   */

   /* Check number of passes match */
   if (shader->passes != ref_shader->passes)
   {
      RARCH_WARN("[Shaders] Passes (number of passes) "
                  "- Current value doesn't match referenced value "
                  "- Full preset will be saved instead of simple preset.\n");
   }
   else
   {
      /* Compare all passes from the shader, if anything is different
       * then we should not save a reference and instead save a
       * full preset instead.
       */
      bool continue_saving_ref                     = true;

      /* Step through each pass comparing all the properties to
       * make sure they match */
      for (i = 0; (i < shader->passes && continue_saving_ref);
            i++)
      {
         const struct video_shader_pass *pass      = &shader->pass[i];
         const struct video_shader_pass *root_pass = &ref_shader->pass[i];
         const struct gfx_fbo_scale *fbo           = &pass->fbo;
         const struct gfx_fbo_scale *root_fbo      = &root_pass->fbo;

         if (!string_is_equal(pass->source.path, root_pass->source.path))
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u path", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && pass->filter != root_pass->filter)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u filter", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && pass->wrap != root_pass->wrap)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u wrap", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && pass->frame_count_mod != root_pass->frame_count_mod)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u frame_count", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && pass->mipmap != root_pass->mipmap)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u mipmap", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && !string_is_equal(pass->alias, root_pass->alias))
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u alias", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && fbo->type_x != root_fbo->type_x)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u type_x", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && fbo->type_y != root_fbo->type_y)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u type_y", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && fbo->scale_x != root_fbo->scale_x)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u scale_x", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && fbo->scale_y != root_fbo->scale_y)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u scale_y", i);
#endif
            continue_saving_ref = false;
         }

         if (      continue_saving_ref
               && (fbo->flags & FBO_SCALE_FLAG_FP_FBO) != (root_fbo->flags & FBO_SCALE_FLAG_FP_FBO))
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u fp_fbo", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref
               && (fbo->flags & FBO_SCALE_FLAG_SRGB_FBO) != (root_fbo->flags &
                  FBO_SCALE_FLAG_SRGB_FBO))
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u srgb_fbo", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref
               && ((fbo->flags & FBO_SCALE_FLAG_VALID) != (root_fbo->flags & FBO_SCALE_FLAG_VALID)))
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u valid", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && fbo->abs_x != root_fbo->abs_x)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u abs_x", i);
#endif
            continue_saving_ref = false;
         }

         if (continue_saving_ref && fbo->abs_y != root_fbo->abs_y)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Pass %u abs_y", i);
#endif
            continue_saving_ref = false;
         }

         if (!continue_saving_ref)
         {
#ifdef DEBUG
            RARCH_WARN("[Shaders] Current value doesn't match referenced value "
                  "- Full preset will be saved instead of simple preset.\n");
#endif
            goto end;
         }
      }
   }

   /* If the shader has parameters */
   if (shader->num_parameters)
   {
      for (i = 0; i < shader->num_parameters; i++)
      {
         /* If the parameter's current value is different
          * than the referenced shader then write the value
          * into the new preset */
         if (     shader->parameters[i].current
               != ref_shader->parameters[i].current)
            config_set_float(conf, shader->parameters[i].id,
                  shader->parameters[i].current);
      }
   }

   /* If the shader has textures */
   if (shader->luts)
   {
      for (i = 0; i < shader->luts; i++)
      {
         /* If the current shader texture path is different
          * than the referenced shader texture then write the
          * current path into the new preset */
         if (!string_is_equal(ref_shader->lut[i].path,
                  shader->lut[i].path))
         {
            char *path_for_save  = (char*)malloc(PATH_MAX_LENGTH);

            fill_pathname_abbreviated_or_relative(path_for_save,
                  conf->path, shader->lut[i].path, PATH_MAX_LENGTH);
            pathname_make_slashes_portable(path_for_save);
            config_set_string(conf, shader->lut[i].id, path_for_save);
#ifdef DEBUG
            RARCH_DBG("[Shaders] Texture override \"%s\" = \"%s\".\n",
                  shader->lut[i].id, path_for_save);
#endif

            free(path_for_save);
         }
      }
   }

   /* Write the file, return will be true if successful */
   RARCH_DBG("[Shaders] Saving simple preset to: \"%s\"\n",
         path_to_save_conformed);
   ret = config_file_write(conf, path_to_save_conformed, false);

end:

   config_file_free(conf);
   config_file_free(ref_conf);
   free(ref_shader);
   free(path_to_ref);
   free(path_to_save_conformed);

   return ret;
}

/**
 * video_shader_load_root_config_into_shader:
 * @param conf
 * Preset file to read from.
 * @param shader
 * Shader handle.
 *
 * Loads preset file and all associated state
 * (passes, textures, imports, etc).
 *
 * @return true (1) if successful, otherwise false (0).
 **/
static bool video_shader_load_root_config_into_shader(
      config_file_t *conf,
      bool video_shader_watch_files,
      struct video_shader *shader)
{
   size_t i;
   unsigned num_passes = 0;

   /* This sets the shader to empty */
   memset(shader, 0, sizeof(*shader));

   if (!config_get_uint(conf, "shaders", &num_passes))
      return false;
   if (!num_passes)
      return false;

   if (!config_get_int(conf, "feedback_pass", &shader->feedback_pass))
      shader->feedback_pass = -1;

   shader->passes = MIN(num_passes, GFX_MAX_SHADERS);

   /* Set the path of the root preset for this shader */
   strlcpy(shader->path, conf->path, sizeof(shader->path));

   /* Set the path of the original preset which was loaded, for
    * a full preset config this is the same as the root config
    * For simple presets (using #reference) this different than
    * the root preset and it is the path to the
    * simple preset originally loaded, but that is set inside
    * video_shader_load_preset_into_shader*/
   strlcpy(shader->loaded_preset_path, conf->path,
         sizeof(shader->loaded_preset_path));

   if (video_shader_watch_files)
   {
      union string_list_elem_attr attr;
      int flags                        =
           PATH_CHANGE_TYPE_MODIFIED
         | PATH_CHANGE_TYPE_WRITE_FILE_CLOSED
         | PATH_CHANGE_TYPE_FILE_MOVED
         | PATH_CHANGE_TYPE_FILE_DELETED;
      struct string_list file_list     = {0};

      attr.i         = 0;

      if (file_change_data)
         frontend_driver_watch_path_for_changes(NULL, 0, &file_change_data);

      file_change_data = NULL;
      string_list_initialize(&file_list);
      string_list_append(&file_list, conf->path, attr);

      /* TODO We aren't currently watching the originally loaded preset
       * We should probably watch it for changes too */

      for (i = 0; i < shader->passes; i++)
      {
         if (!video_shader_parse_pass(conf, &shader->pass[i], (unsigned)i))
         {
            string_list_deinitialize(&file_list);
            return false;
         }

         string_list_append(&file_list, shader->pass[i].source.path, attr);
      }

      frontend_driver_watch_path_for_changes(&file_list, flags,
            &file_change_data);
      string_list_deinitialize(&file_list);
   }
   else
   {
      for (i = 0; i < shader->passes; i++)
      {
         if (!video_shader_parse_pass(conf, &shader->pass[i], (unsigned)i))
            return false;
      }
   }

   if (!video_shader_parse_textures(conf, shader))
      return false;

   /* Load the parameter values */
   video_shader_resolve_parameters(shader);

   /* Load the parameter values */
   video_shader_load_current_parameter_values(conf, shader);

#ifdef DEBUG
   RARCH_DBG("[Shaders] Number of passes: %u.\n", shader->passes);
   RARCH_DBG("[Shaders] Number of textures: %u.\n", shader->luts);

   /* Log Texture Names & Paths */
   for (i = 0; i < shader->luts; i++)
      RARCH_DBG("[Shaders] \"%s\" = \"%s\".\n", shader->lut[i].id,
            shader->lut[i].path);
#endif

   return true;
}

/**
 * video_shader_override_values:
 * @param override_conf
 * Config file who's values will be copied on top of conf
 * @param shader
 * Shader to be affected
 *
 * Takes values from override_config and overrides values of the shader
 *
 * @return 0 if nothing is overridden , 1 if something is overridden
 **/
static bool video_shader_override_values(config_file_t *override_conf,
      struct video_shader *shader)
{
   size_t i;
   bool return_val                     = false;

   if (!shader || !override_conf)
      return 0;

   /* If the shader has parameters */
   if (shader->num_parameters)
   {
      /* Step through the parameters in the shader and
       * see if there is an entry for each in the override config */
      for (i = 0; i < shader->num_parameters; i++)
      {
         /* If the parameter is in the reference config */
         if (config_get_entry(override_conf, shader->parameters[i].id))
         {
            struct video_shader_parameter *parameter =
               (struct video_shader_parameter*)
               video_shader_parse_find_parameter(
                     shader->parameters,
                     shader->num_parameters,
                     shader->parameters[i].id);

            /* Set the shader's parameter value */
            config_get_float(override_conf, shader->parameters[i].id,
                  &parameter->current);

#ifdef DEBUG
            RARCH_DBG("[Shaders] Parameter: \"%s\" = %f.\n",
                  shader->parameters[i].id,
                  shader->parameters[i].current);
#endif

            return_val = true;
         }
      }
   }

   /* ---------------------------------------------------------------------------------
    * ------------- Resolve Override texture paths to absolute paths-------------------
    * --------------------------------------------------------------------------------- */

   /* If the shader has textures */
   if (shader->luts)
   {
      char *override_tex_path = (char*)malloc(PATH_MAX_LENGTH);

      /* Step through the textures in the shader and see if there is an entry
       * for each in the override config */
      for (i = 0; i < shader->luts; i++)
      {
         /* If the texture is defined in the reference config */
         if (config_get_entry(override_conf, shader->lut[i].id))
         {
            char *tex_path = (char*)malloc(PATH_MAX_LENGTH);
            /* Texture path from the config */
            config_get_path(override_conf, shader->lut[i].id, tex_path,
                  PATH_MAX_LENGTH);
            /* Get the absolute path and replace wildcards in the path */
            fill_pathname_expanded_and_absolute(override_tex_path,
                  PATH_MAX_LENGTH, override_conf->path, tex_path);
            /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
            video_shader_replace_wildcards(override_tex_path,
                  PATH_MAX_LENGTH, override_conf->path);
            strlcpy(shader->lut[i].path, override_tex_path,
                  sizeof(shader->lut[i].path));
#ifdef DEBUG
            RARCH_DBG("[Shaders] Texture: \"%s\" = %s.\n",
                        shader->lut[i].id,
                        shader->lut[i].path);
#endif
            free(tex_path);
            return_val = true;
         }
      }

      free(override_tex_path);
   }

   return return_val;
}

static bool video_shader_combine_shaders(
      struct video_shader *combined_shader,
      struct video_shader *first_shader,
      struct video_shader *second_shader)
{
   size_t i, j;

   for (i = 0; i < first_shader->passes && i <= GFX_MAX_SHADERS; i++)
   {
      combined_shader->pass[i] = first_shader->pass[i];
      combined_shader->passes += 1;
   }
   for (i = 0;
        i < second_shader->passes && first_shader->passes + i <= GFX_MAX_SHADERS;
        i++)
   {
      combined_shader->pass[first_shader->passes + i] = second_shader->pass[i];
      combined_shader->passes += 1;
   }

   for (i = 0; i < first_shader->luts; i++)
   {
      strlcpy(combined_shader->lut[i].id,
               first_shader->lut[i].id,
               sizeof(combined_shader->lut[i].id));
      strlcpy(combined_shader->lut[i].path,
               first_shader->lut[i].path,
               sizeof(combined_shader->lut[i].path));
      combined_shader->luts += 1;
   }

   for (i = 0; i < second_shader->luts; i++)
   {
      bool b_name_in_list = false;
      for (j = 0; j < combined_shader->luts; j++)
      {
         if (strncmp(second_shader->lut[i].id, combined_shader->lut[j].id, 64) == 0)
         {
            b_name_in_list = true;
            break;
         }
      }
      if (!b_name_in_list)
      {
         strlcpy(combined_shader->lut[combined_shader->luts].id,
                  second_shader->lut[i].id,
                  sizeof(combined_shader->lut[combined_shader->luts].id));
         strlcpy(combined_shader->lut[combined_shader->luts].path,
                  second_shader->lut[i].path,
                  sizeof(combined_shader->lut[combined_shader->luts].path));
         combined_shader->luts += 1;
      }
   }

   video_shader_resolve_parameters(combined_shader);

   /* Step through the parameters in the shader and see if the parameter is in either of the shaders */
   for (i = 0; i < combined_shader->num_parameters; i++)
   {

      /* Check the original shader */
      if (video_shader_parse_find_parameter(
               first_shader->parameters,
               first_shader->num_parameters,
               combined_shader->parameters[i].id))
         combined_shader->parameters[i].current = video_shader_parse_find_parameter(
               first_shader->parameters,
               first_shader->num_parameters,
               combined_shader->parameters[i].id)->current;

      /* Check the shader to append */
      if (video_shader_parse_find_parameter(
               second_shader->parameters,
               second_shader->num_parameters,
               combined_shader->parameters[i].id))
         combined_shader->parameters[i].current = video_shader_parse_find_parameter(
               second_shader->parameters,
               second_shader->num_parameters,
               combined_shader->parameters[i].id)->current;
   }

   return true;
}

/**
 * video_shader_write_preset:
 * @param path
 * File to write to
 * @param shader
 * Shader to write
 * @param reference
 * Whether a simple preset should be written
 * with the #reference to another preset in it.
 *
 * Writes a preset to disk. Can be written as a simple preset
 * (With the #reference directive in it) or a full preset.
 * @return true on success, otherwise false on failure
 **/
bool video_shader_write_preset(const char *path,
      const struct video_shader *shader,
      bool reference)
{
   if (!shader || string_is_empty(path))
      return false;

   /* If we should still save a referenced preset do it now */
   if (reference)
   {
      if (video_shader_write_referenced_preset(shader, path))
         return true;

      RARCH_WARN("[Shaders] Failed writing simple preset to \"%s\" "
            "- Full preset will be saved instead.\n", path);
   }

   /* If we aren't saving a referenced preset or weren't able to save one
    * then save a full preset */
   if (path)
      return video_shader_write_root_preset(shader, path);
   return false;
}

/**
 * video_shader_load_preset_into_shader:
 * @param path
 * Path to preset file, could be a
 * Simple Preset (including a #reference) or Full Preset.
 * @param shader
 * Shader.
 *
 * Loads preset file to a shader including passes, textures
 * and parameters
 *
 * @return true on success, otherwise false on failure.
 **/
bool video_shader_load_preset_into_shader(const char *path,
      struct video_shader *shader)
{
   bool ret                                          = true;
   config_file_t *conf                               = NULL;
   struct path_linked_list* override_paths_list      = NULL;
   struct path_linked_list* path_list_tmp            = NULL;
   config_file_t *root_conf                          = video_shader_get_root_preset_config(path);

   if (!root_conf)
   {
      RARCH_WARN("[Shaders] Could not read root preset: \"%s\".\n", path);
      return false;
   }

   /* Check if the root preset is a valid shader chain
      If the config has a shaders entry then it is considered
      a shader chain config, vs a config which may only have
      parameter values and texture overrides
   */
   if (!config_get_entry(root_conf, "shaders"))
   {
      RARCH_WARN("[Shaders] Root preset is not a valid shader chain because it has no shaders entry: \"%s\".\n", path);
      ret = false;
      goto end;
   }

  /* If the root_conf path matches the original path then
    * there are no references so we just load it and go to the end */
   if (string_is_equal(root_conf->path, path))
   {
      /* Load the config from the shader chain from the first reference into the shader */
      video_shader_load_root_config_into_shader(root_conf,
            config_get_ptr()->bools.video_shader_watch_files, shader);
      goto end;
   }

   /* Get the config from the initial preset file
    * We don't need to check it's validity because it must
    * have been valid to get the root preset */
   conf = config_file_new_from_path_to_string(path);

#ifdef DEBUG
   RARCH_DBG("[Shaders] Crawl preset reference chain...\n");
#endif

   /**
    * Check references starting with the second to make sure
    * they do not have a shader chains in them
   **/
   path_list_tmp = (struct path_linked_list*)conf->references->next;
   while (path_list_tmp)
   {
      config_file_t *tmp_conf = NULL;
      char *path_to_ref       = (char*)malloc(PATH_MAX_LENGTH);
      /* Get the absolute path and replace wildcards in the path */
      fill_pathname_expanded_and_absolute(path_to_ref, PATH_MAX_LENGTH,
            conf->path, path_list_tmp->path);
      /* TODO/FIXME - dehardcode PATH_MAX_LENGTH */
      video_shader_replace_wildcards(path_to_ref, PATH_MAX_LENGTH, conf->path);

      if ((tmp_conf = video_shader_get_root_preset_config(path_to_ref)))
      {
         /* Check if the config is a valid shader chain config
            If the config has a shaders entry then it is considered
            a shader chain config, vs a config which may only have
            parameter values and texture overrides */
         if (config_get_entry(tmp_conf, "shaders"))
         {
            RARCH_WARN("[Shaders] Additional #reference entries pointing at shader chain presets are not supported: \"%s\".\n", path_to_ref);
            config_file_free(tmp_conf);
            ret = false;
            goto end;
         }
         config_file_free(tmp_conf);
      }
      else
      {
         RARCH_WARN("[Shaders] Could not load root preset for #reference entry: \"%s\".\n", path_to_ref);
         ret = false;
         goto end;
      }
      path_list_tmp = path_list_tmp->next;
   }

   /* Load the config from the shader chain from the first reference into the shader */
   video_shader_load_root_config_into_shader(root_conf,
         config_get_ptr()->bools.video_shader_watch_files, shader);

   /* Set Path for originally loaded preset because it is different than the root preset path */
   strlcpy(shader->loaded_preset_path, path, sizeof(shader->loaded_preset_path));

#ifdef DEBUG
   RARCH_DBG("[Shaders] Start applying simple preset overrides...\n");
#endif

   /* Gather all the paths of all of the presets in all reference chains */
   override_paths_list = path_linked_list_new();
   video_shader_gather_reference_path_list(override_paths_list, conf->path, 0);

   /* Step through the references and apply overrides for each one
    * Start on the second item since the first is empty */
   path_list_tmp = (struct path_linked_list*)override_paths_list;
   while (path_list_tmp)
   {
      config_file_t *override_conf = config_file_new_from_path_to_string(path_list_tmp->path);
#ifdef DEBUG
      RARCH_DBG("[Shaders] Apply values from: \"%s\".\n", override_conf->path);
#endif
      video_shader_override_values(override_conf, shader);
      config_file_free(override_conf);
      path_list_tmp = path_list_tmp->next;
   }

#ifdef DEBUG
   RARCH_DBG("[Shaders] End apply overrides.\n\n");
#endif

end:
   path_linked_list_free(override_paths_list);
   config_file_free(conf);
   config_file_free(root_conf);

   return ret;
}

const char *video_shader_type_to_str(enum rarch_shader_type type)
{
   switch (type)
   {
      case RARCH_SHADER_CG:
         return "Cg";
      case RARCH_SHADER_HLSL:
         return "HLSL";
      case RARCH_SHADER_GLSL:
         return "GLSL";
      case RARCH_SHADER_SLANG:
         return "Slang";
      case RARCH_SHADER_METAL:
         return "Metal";
      case RARCH_SHADER_NONE:
         return "none";
      default:
         break;
   }

   return "???";
}

enum display_flags video_shader_type_to_flag(enum rarch_shader_type type)
{
   switch (type)
   {
      case RARCH_SHADER_SLANG:
         return GFX_CTX_FLAGS_SHADERS_SLANG;
      case RARCH_SHADER_GLSL:
         return GFX_CTX_FLAGS_SHADERS_GLSL;
      case RARCH_SHADER_CG:
         return GFX_CTX_FLAGS_SHADERS_CG;
      case RARCH_SHADER_HLSL:
         return GFX_CTX_FLAGS_SHADERS_HLSL;
      case RARCH_SHADER_NONE:
      default:
         break;
   }
   return GFX_CTX_FLAGS_NONE;
}


const char *video_shader_get_preset_extension(enum rarch_shader_type type)
{
   switch (type)
   {
      case RARCH_SHADER_GLSL:
         return ".glslp";
      case RARCH_SHADER_SLANG:
         return ".slangp";
      case RARCH_SHADER_HLSL:
      case RARCH_SHADER_CG:
         return ".cgp";
      default:
         break;
   }

   return NULL;
}

enum rarch_shader_type video_shader_get_type_from_ext(
      const char *ext, bool *is_preset)
{
   if (string_is_empty(ext))
      return RARCH_SHADER_NONE;

   if ((ext[0] != '\0') && (ext[0] == '.') && (ext[1] != '\0'))
      ext++;

   if (is_preset)
      *is_preset =
            string_is_equal_case_insensitive(ext, "cgp")
         || string_is_equal_case_insensitive(ext, "glslp")
         || string_is_equal_case_insensitive(ext, "slangp");

   if (     string_is_equal_case_insensitive(ext, "cgp")
         || string_is_equal_case_insensitive(ext, "cg")
      )
      return RARCH_SHADER_CG;
   if (     string_is_equal_case_insensitive(ext, "glslp")
         || string_is_equal_case_insensitive(ext, "glsl")
      )
      return RARCH_SHADER_GLSL;
   if (     string_is_equal_case_insensitive(ext, "slangp")
         || string_is_equal_case_insensitive(ext, "slang")
      )
      return RARCH_SHADER_SLANG;
   return RARCH_SHADER_NONE;
}

bool video_shader_check_for_changes(void)
{
   if (!file_change_data)
      return false;

   return frontend_driver_check_for_path_changes(file_change_data);
}

void video_shader_dir_free_shader(
      struct rarch_dir_shader_list *dir_list,
      bool shader_remember_last_dir)
{
   if (dir_list->shader_list)
   {
      dir_list_free(dir_list->shader_list);
      dir_list->shader_list = NULL;
   }

   if (dir_list->directory)
   {
      free(dir_list->directory);
      dir_list->directory = NULL;
   }

   dir_list->selection                = 0;
   dir_list->shader_loaded            = false;
   dir_list->remember_last_preset_dir = shader_remember_last_dir;
}

static bool video_shader_dir_init_shader_internal(
      bool shader_remember_last_dir,
      struct rarch_dir_shader_list *dir_list,
      const char *shader_dir,
      const char *shader_file_name,
      bool show_hidden_files)
{
   size_t i;
   struct string_list *new_list       = dir_list_new_special(
         shader_dir, DIR_LIST_SHADERS, NULL, show_hidden_files);
   bool search_file_name              = shader_remember_last_dir
         && !string_is_empty(shader_file_name);

   if (!new_list)
      return false;

   if (new_list->size < 1)
   {
      dir_list_free(new_list);
      return false;
   }

   dir_list_sort(new_list, false);

   dir_list->shader_list              = new_list;
   dir_list->directory                = strdup(shader_dir);
   dir_list->selection                = 0;
   dir_list->shader_loaded            = false;
   dir_list->remember_last_preset_dir = shader_remember_last_dir;

   if (search_file_name)
   {
      for (i = 0; i < new_list->size; i++)
      {
         const char *file_name = NULL;
         const char *file_path = new_list->elems[i].data;

         if (string_is_empty(file_path))
            continue;

         /* If a shader file name has been provided,
          * search the list for a match and set 'selection'
          * index if found */
         file_name = path_basename(file_path);

         if (    !string_is_empty(file_name)
               && string_is_equal(file_name, shader_file_name))
         {
            RARCH_LOG("[Shaders] %s \"%s\".\n",
                  msg_hash_to_str(MSG_FOUND_SHADER),
                  file_path);

            dir_list->selection = i;
            break;
         }
      }
   }

   return true;
}

static void video_shader_dir_init_shader(
      void *menu_driver_data_,
      const char *directory_video_shader,
      const char *directory_menu_config,
      bool show_hidden_files,
      bool shader_remember_last_dir,
      bool video_shader_remember_last_dir,
      struct rarch_dir_shader_list *dir_list)
{
   const char *last_shader_preset_dir             = NULL;
   const char *last_shader_preset_file_name       = NULL;
   video_driver_state_t *video_st                 = video_state_get_ptr();
   enum rarch_shader_type last_shader_preset_type = RARCH_SHADER_NONE;
#if defined(HAVE_MENU)
   menu_handle_t *menu                            = (menu_handle_t*)menu_driver_data_;
   if (menu)
      last_shader_preset_type                     = menu->last_shader_selection.preset_type;
   menu_driver_get_last_shader_preset_path(
         &last_shader_preset_dir, &last_shader_preset_file_name);
#endif

   /* Always free existing shader list */
   video_shader_dir_free_shader(dir_list,
         video_shader_remember_last_dir);

   /* Try directory of last selected shader preset */
   if (    shader_remember_last_dir
       && (last_shader_preset_type != RARCH_SHADER_NONE)
       && !string_is_empty(last_shader_preset_dir)
       && video_shader_dir_init_shader_internal(
          video_shader_remember_last_dir,
          dir_list,
          last_shader_preset_dir,
          last_shader_preset_file_name,
          show_hidden_files))
      return;

   /* Try directory of shader given as command line parameter */
   if (!string_is_empty(video_st->cli_shader_path))
   {
      char cli_path[PATH_MAX_LENGTH];
      fill_pathname_basedir(cli_path, video_st->cli_shader_path, sizeof(cli_path));
      if (video_shader_dir_init_shader_internal(
               video_shader_remember_last_dir,
               dir_list,
               cli_path, NULL, show_hidden_files))
         return;
   }

   /* Try video shaders directory */
   if (  !string_is_empty(directory_video_shader)
       && video_shader_dir_init_shader_internal(
            video_shader_remember_last_dir,
            dir_list,
            directory_video_shader, NULL, show_hidden_files))
      return;

   /* Try config directory */
   if (  !string_is_empty(directory_menu_config)
       && video_shader_dir_init_shader_internal(
            video_shader_remember_last_dir,
            dir_list,
            directory_menu_config, NULL, show_hidden_files))
      return;

   /* Try 'top level' directory containing main
    * RetroArch config file */
   if (!path_is_empty(RARCH_PATH_CONFIG))
   {
      char *rarch_config_directory = strdup(path_get(RARCH_PATH_CONFIG));
      path_basedir(rarch_config_directory);

      if (!string_is_empty(rarch_config_directory))
         video_shader_dir_init_shader_internal(
               video_shader_remember_last_dir,
               dir_list,
               rarch_config_directory, NULL, show_hidden_files);

      free(rarch_config_directory);
   }
}

void video_shader_dir_check_shader(
      void *menu_driver_data_,
      settings_t *settings,
      struct rarch_dir_shader_list *dir_list,
      bool pressed_next,
      bool pressed_prev)
{
   bool video_shader_remember_last_dir            = settings->bools.video_shader_remember_last_dir;
   const char *last_shader_preset_dir             = NULL;
   const char *last_shader_preset_file_name       = NULL;
   const char *set_shader_path                    = NULL;
   bool dir_list_initialised                      = false;
   enum rarch_shader_type last_shader_preset_type = RARCH_SHADER_NONE;
#if defined(HAVE_MENU)
   void *menu_ptr                                 = menu_driver_data_;
   menu_handle_t *menu                            = (menu_handle_t*)menu_ptr;
   if (menu)
      last_shader_preset_type                     = menu->last_shader_selection.preset_type;
   menu_driver_get_last_shader_preset_path(
         &last_shader_preset_dir, &last_shader_preset_file_name);
#else
   void *menu_ptr                                 = NULL;
#endif

   /* Check whether shader list needs to be (re)initialised */
   if (   !dir_list->shader_list
       || (dir_list->remember_last_preset_dir != video_shader_remember_last_dir)
       || (video_shader_remember_last_dir
       && (last_shader_preset_type != RARCH_SHADER_NONE)
       && !string_is_equal(dir_list->directory, last_shader_preset_dir)))
   {
      const char *directory_video_shader          = settings->paths.directory_video_shader;
      const char *directory_menu_config           = settings->paths.directory_menu_config;
      bool show_hidden_files                      = settings->bools.show_hidden_files;
      bool shader_remember_last_dir               = settings->bools.video_shader_remember_last_dir;
      bool video_shader_remember_last_dir         = settings->bools.video_shader_remember_last_dir;
      video_shader_dir_init_shader(menu_ptr,
            directory_video_shader,
            directory_menu_config,
            show_hidden_files,
            shader_remember_last_dir,
            video_shader_remember_last_dir,
            dir_list);
      dir_list_initialised = true;
   }

   if (   !dir_list->shader_list
       || (dir_list->shader_list->size < 1))
      return;

   /* Check whether a 'last used' shader file
    * name is provided
    * > Note: We can end up calling
    *   string_is_equal(dir_list->directory, last_shader_preset_dir)
    *   twice. This is wasteful, but we cannot safely cache
    *   the first result since video_shader_dir_init_shader() is called
    *   in-between the two invocations... */
   if (    video_shader_remember_last_dir
       && (last_shader_preset_type != RARCH_SHADER_NONE)
       && string_is_equal(dir_list->directory, last_shader_preset_dir)
       && !string_is_empty(last_shader_preset_file_name))
   {
      /* Ensure that we start with a dir_list selection
       * index matching the last used shader */
      if (!dir_list_initialised)
      {
         const char *current_file_path = NULL;
         const char *current_file_name = NULL;

         if (dir_list->selection < dir_list->shader_list->size)
            current_file_path = dir_list->shader_list->elems[dir_list->selection].data;

         if (!string_is_empty(current_file_path))
            current_file_name = path_basename(current_file_path);

         if (   !string_is_empty(current_file_name)
             && !string_is_equal(current_file_name, last_shader_preset_file_name))
         {
            size_t i;
            for (i = 0; i < dir_list->shader_list->size; i++)
            {
               const char *file_path = dir_list->shader_list->elems[i].data;
               const char *file_name = NULL;

               if (string_is_empty(file_path))
                  continue;

               file_name = path_basename(file_path);

               if (string_is_empty(file_name))
                  continue;

               if (string_is_equal(file_name, last_shader_preset_file_name))
               {
                  dir_list->selection = i;
                  break;
               }
            }
         }
      }

#ifdef HAVE_MENU
      /* Check whether the shader referenced by the
       * current selection index is already loaded */
      if (!dir_list->shader_loaded)
      {
         struct video_shader *shader = menu_shader_get();

         if (shader && !string_is_empty(shader->loaded_preset_path))
         {
            char last_shader_path[PATH_MAX_LENGTH];
            fill_pathname_join(last_shader_path,
                  last_shader_preset_dir, last_shader_preset_file_name,
                  sizeof(last_shader_path));

            if (string_is_equal(last_shader_path, shader->loaded_preset_path))
               dir_list->shader_loaded = true;
         }
      }
#endif
   }

   /* Select next shader in list */
   if (pressed_next)
   {
      /* Only increment selection if a shader
       * from this list has already been loaded
       * (otherwise first entry in the list may
       * be skipped) */
      if (dir_list->shader_loaded)
      {
         if (dir_list->selection < dir_list->shader_list->size - 1)
            dir_list->selection++;
         else
            dir_list->selection = 0;
      }
   }
   /* Select previous shader in list */
   else if (pressed_prev)
   {
      if (dir_list->selection > 0)
         dir_list->selection--;
      else
         dir_list->selection = dir_list->shader_list->size - 1;
   }
   else
      return;

   set_shader_path = dir_list->shader_list->elems[dir_list->selection].data;
#if defined(HAVE_MENU)
   menu_driver_set_last_shader_preset_path(set_shader_path);
#endif
   command_set_shader(NULL, set_shader_path);
   dir_list->shader_loaded = true;
}

static bool video_shader_load_shader_preset_internal(
      char *s,
      size_t len,
      const char *shader_directory,
      const char *core_name,
      const char *special_name)
{
   int i;
   gfx_ctx_flags_t flags;
   static enum rarch_shader_type types[] =
   {
      /* Shader preset priority, highest to lowest
       * only important for video drivers with multiple shader backends */
      RARCH_SHADER_SLANG, RARCH_SHADER_GLSL, RARCH_SHADER_CG, RARCH_SHADER_HLSL
   };

   flags.flags     = 0;
   video_context_driver_get_flags(&flags);

   for (i = 0; i < (int)ARRAY_SIZE(types); i++)
   {
      if (!BIT32_GET(flags.flags, video_shader_type_to_flag(types[i])))
            continue;

      /* Concatenate strings into full paths */
      if (!string_is_empty(core_name))
         fill_pathname_join_special_ext(s,
               shader_directory, core_name,
               special_name,
               video_shader_get_preset_extension(types[i]),
               len);
      else
      {
         size_t _len;
         if (string_is_empty(special_name))
            break;

         _len = fill_pathname_join(s, shader_directory, special_name, len);
         strlcpy(s + _len, video_shader_get_preset_extension(types[i]), len - _len);
      }

      if (path_is_valid(s))
         return true;
   }

   return false;
}

/**
 * video_shader_load_auto_shader_preset:
 *
 * Tries to load a supported core-, game-, folder-specific or global
 * shader preset from its respective location:
 *
 * global:          $CONFIG_DIR/global.$PRESET_EXT
 * core-specific:   $CONFIG_DIR/$CORE_NAME/$CORE_NAME.$PRESET_EXT
 * folder-specific: $CONFIG_DIR/$CORE_NAME/$FOLDER_NAME.$PRESET_EXT
 * game-specific:   $CONFIG_DIR/$CORE_NAME/$GAME_NAME.$PRESET_EXT
 *
 * $CONFIG_DIR is expected to be Menu Config directory, or failing that, the
 * directory where retroarch.cfg is stored.
 *
 * For compatibility purposes with versions 1.8.7 and older, the presets
 * subdirectory on the Video Shader path is used as a fallback directory.
 *
 * Note: Uses video_context_driver_get_flags() which only works after
 *       context driver initialization.
 *
 * Returns: false if there was an error or no action was performed.
 */
static size_t video_shader_load_auto_shader_preset(
      const char *video_shader_directory,
      const char *menu_config_directory,
      const char *core_name,
      char *s, size_t len)
{
   size_t i;
   const char *rarch_path_basename    = path_get(RARCH_PATH_BASENAME);
   bool has_content                   = !string_is_empty(rarch_path_basename);

   const char *game_name              = NULL;
   const char *dirs[3]                = {0};

   char shader_path[PATH_MAX_LENGTH];
   char content_dir_name[DIR_MAX_LENGTH];
   char config_file_directory[DIR_MAX_LENGTH];
   char old_presets_directory[DIR_MAX_LENGTH];

   shader_path[0]                     = '\0';
   content_dir_name[0]                = '\0';
   config_file_directory[0]           = '\0';
   old_presets_directory[0]           = '\0';

   if (has_content)
   {
      fill_pathname_parent_dir_name(content_dir_name,
            rarch_path_basename, sizeof(content_dir_name));
      game_name = path_basename(rarch_path_basename);
   }

   if (!path_is_empty(RARCH_PATH_CONFIG))
      fill_pathname_basedir(config_file_directory,
            path_get(RARCH_PATH_CONFIG), sizeof(config_file_directory));

   if (!string_is_empty(video_shader_directory))
      fill_pathname_join(old_presets_directory,
         video_shader_directory, "presets", sizeof(old_presets_directory));

   dirs[0]                            = menu_config_directory;
   dirs[1]                            = config_file_directory;
   dirs[2]                            = old_presets_directory;

   for (i = 0; i < ARRAY_SIZE(dirs); i++)
   {
      if (string_is_empty(dirs[i]))
         continue;

      if (has_content)
      {
         /* Game-specific shader preset found? */
         if (video_shader_load_shader_preset_internal(
                  shader_path,
                  sizeof(shader_path),
                  dirs[i], core_name,
                  game_name))
            return strlcpy(s, shader_path, len);

         /* Folder-specific shader preset found? */
         if (video_shader_load_shader_preset_internal(
                  shader_path,
                  sizeof(shader_path),
                  dirs[i], core_name,
                  content_dir_name))
            return strlcpy(s, shader_path, len);
      }

      /* Core-specific shader preset found? */
      if (video_shader_load_shader_preset_internal(
               shader_path,
               sizeof(shader_path),
               dirs[i], core_name,
               core_name))
         return strlcpy(s, shader_path, len);

      /* Global shader preset found? */
      if (video_shader_load_shader_preset_internal(
               shader_path,
               sizeof(shader_path),
               dirs[i], NULL,
               "global"))
         return strlcpy(s, shader_path, len);
   }

   return 0;
}

bool video_shader_combine_preset_and_apply(
      enum rarch_shader_type type,
      struct video_shader *menu_shader,
      const char *preset_path,
      const char *temp_dir,
      bool prepend,
      bool message)
{
   bool ret = false;
   char combined_preset_path[PATH_MAX_LENGTH];
   char combined_preset_name[NAME_MAX_LENGTH];
   const char *preset_ext                = video_shader_get_preset_extension(type);
   struct video_shader *shader_to_append = (struct video_shader*) calloc(1, sizeof(*shader_to_append));
   struct video_shader *combined_shader  = (struct video_shader*) calloc(1, sizeof(*combined_shader));
   size_t _len = strlcpy(combined_preset_name, "retroarch", sizeof(combined_preset_name));
   strlcpy(combined_preset_name + _len, preset_ext, sizeof(combined_preset_name) - _len);
   fill_pathname_join(combined_preset_path, temp_dir, combined_preset_name, sizeof(combined_preset_path));

   video_shader_load_preset_into_shader(preset_path, shader_to_append);

   if (prepend)
      video_shader_combine_shaders(combined_shader, shader_to_append, menu_shader);
   else
      video_shader_combine_shaders(combined_shader, menu_shader, shader_to_append);

   /* We save to the always changing retroarch.slangp, retroarch.glsp...
      then load it so that it is similar to Apply Preset */
   video_shader_write_preset(combined_preset_path, combined_shader, false);

#ifdef HAVE_MENU
   ret = menu_shader_manager_set_preset(menu_shader, type, combined_preset_path, true);
#else
   ret = true;
#endif

   free(shader_to_append);
   free(combined_shader);

   return ret;
}

/* Sets and loads the preset in the video driver */
/* Applies the preset to the menu */
bool video_shader_apply_shader(
      settings_t *settings,
      enum rarch_shader_type type,
      const char *preset_path,
      bool message)
{
   size_t _len;
   char msg[NAME_MAX_LENGTH];
   video_driver_state_t *video_st = video_state_get_ptr();
   runloop_state_t *runloop_st    = runloop_state_get_ptr();
   const char      *core_name     = runloop_st->system.info.library_name;
   const char      *preset_file   = NULL;
#ifdef HAVE_MENU
   struct video_shader *shader    = menu_shader_get();
#endif

   /* Disallow loading shaders when no core is loaded */
   if (string_is_empty(core_name))
      return false;

   if (!string_is_empty(preset_path))
      preset_file = path_basename_nocompression(preset_path);

   /* TODO/FIXME - This loads the shader into the video driver
    * But then we load the shader from disk twice more to put it in the menu
    * We need to reconfigure this at some point to only load it once */
   if (video_st->current_video->set_shader)
   {
      if ((video_st->current_video->set_shader(
                  video_st->data, type, preset_path)))
      {
         if (!string_is_empty(preset_path))
         {
            if (runloop_st->runtime_shader_preset_path != preset_path)
               strlcpy(runloop_st->runtime_shader_preset_path, preset_path,
                     sizeof(runloop_st->runtime_shader_preset_path));
#ifdef HAVE_MENU
            /* reflect in shader manager */
            if (menu_shader_manager_set_preset(
                     shader, type, preset_path, false))
               shader->flags &= ~SHDR_FLAG_MODIFIED;
#endif
         }
         else
            runloop_st->runtime_shader_preset_path[0] = '\0';

         if (message)
         {
            /* Display message */
            const char *msg_shader = msg_hash_to_str(MSG_SHADER);
            size_t _len            = strlcpy(msg, msg_shader, sizeof(msg));
            msg[  _len]            = ':';
            msg[++_len]            = ' ';
            if (preset_file)
            {
               msg[++_len]         = '"';
               msg[++_len]         = '\0';
               _len               += strlcpy(msg + _len,
                     preset_file,    sizeof(msg) - _len);
               msg[  _len]         = '"';
               msg[++_len]         = '\0';
            }
            else
            {
               msg[++_len]         = '\0';
               _len += strlcpy(msg       + _len,
                     msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE),
                     sizeof(msg) - _len);
            }

#ifdef HAVE_GFX_WIDGETS
            if (dispwidget_get_ptr()->active)
               gfx_widget_set_generic_message(msg, 2000);
            else
#endif
               runloop_msg_queue_push(msg, _len, 1, 120, true, NULL,
                     MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
         }

         RARCH_LOG("[Shaders] %s: \"%s\".\n",
               msg_hash_to_str(MSG_APPLYING_SHADER),
               preset_path ? preset_path : "null");

         return true;
      }
   }

#ifdef HAVE_MENU
   /* reflect in shader manager */
   menu_shader_manager_set_preset(shader, type, NULL, false);
#endif

   /* Display error message */
   _len = fill_pathname_join_delim(msg,
         msg_hash_to_str(MSG_FAILED_TO_APPLY_SHADER_PRESET),
         preset_file ? preset_file : "null",
         ' ',
         sizeof(msg));

   runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
         MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
   return false;
}

/* get the name of the current shader preset */
const char *video_shader_get_current_shader_preset(void)
{
   settings_t *settings           = config_get_ptr();
   runloop_state_t *runloop_st    = runloop_state_get_ptr();
   video_driver_state_t *video_st = video_state_get_ptr();
   const char *core_name          = runloop_st->system.info.library_name;
   bool video_shader_enable       = settings->bools.video_shader_enable;
   unsigned video_shader_delay    = settings->uints.video_shader_delay;
   bool auto_shaders_enable       = settings->bools.auto_shaders_enable;
   bool cli_shader_disable        = (video_st->flags &
      VIDEO_FLAG_CLI_SHADER_DISABLE) ? true : false;

   if (!video_shader_enable)
      return NULL;

   if (video_shader_delay && !runloop_st->shader_delay_timer.timer_end)
      return NULL;

   /* Disallow loading auto-shaders when no core is loaded */
   if (string_is_empty(core_name))
      return NULL;

   if (!string_is_empty(runloop_st->runtime_shader_preset_path))
      return runloop_st->runtime_shader_preset_path;

   /* load auto-shader once, --set-shader works like a global auto-shader */
   if (     (video_st->flags & VIDEO_FLAG_SHADER_PRESETS_NEED_RELOAD)
         && !cli_shader_disable)
   {
      gfx_ctx_flags_t flags;
      flags.flags     = 0;
      video_context_driver_get_flags(&flags);

      video_st->flags &= ~VIDEO_FLAG_SHADER_PRESETS_NEED_RELOAD;

      if (BIT32_GET(flags.flags,
               video_shader_type_to_flag(video_shader_parse_type(video_st->cli_shader_path))))
         strlcpy(runloop_st->runtime_shader_preset_path,
               video_st->cli_shader_path,
               sizeof(runloop_st->runtime_shader_preset_path));
      else
      {
         if (auto_shaders_enable) /* sets runtime_shader_preset_path */
         {
            if (video_shader_load_auto_shader_preset(
                     settings->paths.directory_video_shader,
                     settings->paths.directory_menu_config,
                     runloop_st->system.info.library_name,
                     runloop_st->runtime_shader_preset_path,
                     sizeof(runloop_st->runtime_shader_preset_path)))
            {
               RARCH_LOG("[Shaders] Specific shader preset found at \"%s\".\n",
                     runloop_st->runtime_shader_preset_path);
            }
         }
      }
      return runloop_st->runtime_shader_preset_path;
   }

   return NULL;
}

void video_shader_toggle(settings_t *settings, bool write)
{
   bool enabled                    = settings->bools.video_shader_enable;
#ifdef HAVE_MENU
   struct video_shader *menu_shdr  = menu_shader_get();
   struct menu_state *menu_st      = menu_state_get_ptr();

   menu_shdr->flags               &= ~SHDR_FLAG_MODIFIED;
   menu_shdr->flags               &= ~SHDR_FLAG_TEMPORARY;
#endif

   /* Cold start from hotkey requires enabling shaders initially */
   if (!write && !enabled)
   {
      write   = true;
      enabled = true;
      configuration_set_bool(settings, settings->bools.video_shader_enable, true);
   }
#ifdef HAVE_MENU
   else if (!write)
      enabled = (menu_shdr->flags & SHDR_FLAG_DISABLED);
#endif

#ifdef HAVE_MENU
   if (enabled)
      menu_shdr->flags            &= ~SHDR_FLAG_DISABLED;
   else
      menu_shdr->flags            |=  SHDR_FLAG_DISABLED;

   if (!write && video_driver_test_all_flags(GFX_CTX_FLAGS_FAST_TOGGLE_SHADERS))
      menu_shdr->flags            |=  SHDR_FLAG_TEMPORARY;

   menu_st->flags                 |=  MENU_ST_FLAG_ENTRIES_NEED_REFRESH
                                   |  MENU_ST_FLAG_PREVENT_POPULATE;
#endif

   command_event(CMD_EVENT_SHADERS_APPLY_CHANGES, NULL);
}
