/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
 *  Copyright (C) 2011-2017 - Daniel De Matteis
 *  Copyright (C) 2012-2014 - OV2
 *
 *  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/>.
 */

/* Direct3D 9 driver with HLSL runtime backend.
 *
 * Minimum version : Direct3D 9.0 (2002)
 * Minimum OS      : Windows 98, Windows 2000, Windows ME
 * Recommended OS  : Windows XP
 * Requirements    : HLSL or fixed function backend
 */

#define CINTERFACE

#ifdef _XBOX
#include <xtl.h>
#include <xgraphics.h>
#endif

#include <formats/image.h>
#include <compat/strl.h>
#include <compat/posix_string.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#include <retro_math.h>

#include <string.h>
#include <retro_inline.h>
#include <retro_math.h>

#include <d3d9.h>
#include <d3dx9shader.h>

#include "d3d_shaders/opaque.hlsl.d3d9.h"
#include "d3d9_renderchain.h"

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

#include <defines/d3d_defines.h>
#include "../common/d3d_common.h"
#include "../common/d3d9_common.h"
#include "../video_driver.h"
#include "../../configuration.h"
#include "../../dynamic.h"
#include "../../frontend/frontend_driver.h"

#include "../common/win32_common.h"

#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif
#ifdef HAVE_GFX_WIDGETS
#include "../gfx_widgets.h"
#endif

#include "../font_driver.h"

#include "../../core.h"
#include "../../verbosity.h"
#include "../../retroarch.h"

#ifdef __WINRT__
#error "UWP does not support D3D9"
#endif

/* TODO/FIXME - Temporary workaround for D3D9 not being able to poll flags during init */
static gfx_ctx_driver_t d3d9_hlsl_fake_context;

/* BEGIN HLSL RENDERCHAIN */

#define RARCH_HLSL_MAX_SHADERS 16

typedef struct hlsl_renderchain
{
   struct d3d9_renderchain chain;
   struct shader_pass stock_shader;
} hlsl_renderchain_t;

/*
 * DISPLAY DRIVER
 */

static const float d3d9_hlsl_vertexes[8] = {
   0, 0,
   1, 0,
   0, 1,
   1, 1
};

static const float d3d9_hlsl_tex_coords[8] = {
   0, 1,
   1, 1,
   0, 0,
   1, 0
};

static const float *gfx_display_d3d9_hlsl_get_default_vertices(void)
{
   return &d3d9_hlsl_vertexes[0];
}

static const float *gfx_display_d3d9_hlsl_get_default_tex_coords(void)
{
   return &d3d9_hlsl_tex_coords[0];
}

static void *gfx_display_d3d9_hlsl_get_default_mvp(void *data)
{
   static float id[16] =       { 1.0f, 0.0f, 0.0f, 0.0f,
                                 0.0f, 1.0f, 0.0f, 0.0f,
                                 0.0f, 0.0f, 1.0f, 0.0f,
                                 0.0f, 0.0f, 0.0f, 1.0f
                               };
   return &id;
}

static INT32 gfx_display_prim_to_d3d9_hlsl_enum(
      enum gfx_display_prim_type prim_type)
{
   switch (prim_type)
   {
      case GFX_DISPLAY_PRIM_TRIANGLES:
      case GFX_DISPLAY_PRIM_TRIANGLESTRIP:
         return D3DPT_COMM_TRIANGLESTRIP;
      case GFX_DISPLAY_PRIM_NONE:
      default:
         break;
   }

   /* TODO/FIXME - hack */
   return 0;
}

static void gfx_display_d3d9_hlsl_blend_begin(void *data)
{
   d3d9_video_t *d3d = (d3d9_video_t*)data;

   if (!d3d)
      return;

   IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
   IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
   IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_ALPHABLENDENABLE, true);
}

static void gfx_display_d3d9_hlsl_blend_end(void *data)
{
   d3d9_video_t *d3d = (d3d9_video_t*)data;

   if (d3d)
      IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_ALPHABLENDENABLE, false);
}

static void gfx_display_d3d9_bind_texture(gfx_display_ctx_draw_t *draw,
      d3d9_video_t *d3d)
{
   LPDIRECT3DDEVICE9 dev = d3d->dev;

   IDirect3DDevice9_SetTexture(dev, 0,
         (IDirect3DBaseTexture9*)draw->texture);
   IDirect3DDevice9_SetSamplerState(dev,
         0, D3DSAMP_ADDRESSU, D3DTADDRESS_COMM_CLAMP);
   IDirect3DDevice9_SetSamplerState(dev,
         0, D3DSAMP_ADDRESSV, D3DTADDRESS_COMM_CLAMP);
   IDirect3DDevice9_SetSamplerState(dev,
         0, D3DSAMP_MINFILTER, D3DTEXF_COMM_LINEAR);
   IDirect3DDevice9_SetSamplerState(dev,
         0, D3DSAMP_MAGFILTER, D3DTEXF_COMM_LINEAR);
   IDirect3DDevice9_SetSamplerState(dev, 0,
         D3DSAMP_MIPFILTER, D3DTEXF_COMM_LINEAR);
}

static void gfx_display_d3d9_hlsl_draw(gfx_display_ctx_draw_t *draw,
      void *data, unsigned video_width, unsigned video_height)
{
   unsigned i;
   math_matrix_4x4 mop, m1, m2;
   LPDIRECT3DDEVICE9 dev;
   D3DPRIMITIVETYPE type;
   unsigned start                = 0;
   unsigned count                = 0;
   d3d9_video_t *d3d             = (d3d9_video_t*)data;
   Vertex * pv                   = NULL;
   const float *vertex           = NULL;
   const float *tex_coord        = NULL;
   const float *color            = NULL;

   if (!d3d || !draw || draw->pipeline_id)
      return;

   dev                           = d3d->dev;

   if ((d3d->menu_display.offset + draw->coords->vertices )
         > (unsigned)d3d->menu_display.size)
      return;

   IDirect3DVertexBuffer9_Lock((LPDIRECT3DVERTEXBUFFER9)
            d3d->menu_display.buffer, 0, 0, (void**)&pv, 0);
   if (!pv)
      return;

   pv          += d3d->menu_display.offset;
   vertex       = draw->coords->vertex;
   tex_coord    = draw->coords->tex_coord;
   color        = draw->coords->color;

   if (!vertex)
      vertex    = &d3d9_hlsl_vertexes[0];
   if (!tex_coord)
      tex_coord = &d3d9_hlsl_tex_coords[0];

   for (i = 0; i < draw->coords->vertices; i++)
   {
      int colors[4];

      colors[0]   = *color++ * 0xFF;
      colors[1]   = *color++ * 0xFF;
      colors[2]   = *color++ * 0xFF;
      colors[3]   = *color++ * 0xFF;

      pv[i].x     = *vertex++;
      pv[i].y     = *vertex++;
      pv[i].z     = 0.5f;
      pv[i].u     = *tex_coord++;
      pv[i].v     = *tex_coord++;

      pv[i].color =
         D3DCOLOR_ARGB(
               colors[3], /* A */
               colors[0], /* R */
               colors[1], /* G */
               colors[2]  /* B */
               );
   }
   IDirect3DVertexBuffer9_Unlock((LPDIRECT3DVERTEXBUFFER9)
         d3d->menu_display.buffer);

   if (!draw->matrix_data)
      draw->matrix_data = gfx_display_d3d9_hlsl_get_default_mvp(d3d);

   /* ugh */
   matrix_4x4_scale(m1,       2.0,  2.0, 0);
   matrix_4x4_translate(mop, -1.0, -1.0, 0);
   matrix_4x4_multiply(m2, mop, m1);
   matrix_4x4_multiply(m1,
         *((math_matrix_4x4*)draw->matrix_data), m2);
   matrix_4x4_scale(mop,
         (draw->width  / 2.0) / video_width,
         (draw->height / 2.0) / video_height, 0);
   matrix_4x4_multiply(m2, mop, m1);
   matrix_4x4_translate(mop,
         (draw->x + (draw->width  / 2.0)) / video_width,
         (draw->y + (draw->height / 2.0)) / video_height,
         0);
   matrix_4x4_multiply(m1, mop, m2);
   matrix_4x4_multiply(m2, d3d->mvp_transposed, m1);

   IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev,
         0, (const float*)&m2, 4);

   if (draw && draw->texture)
      gfx_display_d3d9_bind_texture(draw, d3d);

   type  = (D3DPRIMITIVETYPE)gfx_display_prim_to_d3d9_hlsl_enum(draw->prim_type);
   start = d3d->menu_display.offset;
   count = draw->coords->vertices -
         ((draw->prim_type == GFX_DISPLAY_PRIM_TRIANGLESTRIP)
          ? 2 : 0);

   IDirect3DDevice9_BeginScene(dev);
   IDirect3DDevice9_DrawPrimitive(dev, type, start, count);
   IDirect3DDevice9_EndScene(dev);

   d3d->menu_display.offset += draw->coords->vertices;
}

static void gfx_display_d3d9_hlsl_draw_pipeline(
      gfx_display_ctx_draw_t *draw,
      gfx_display_t *p_disp,
      void *data, unsigned video_width, unsigned video_height)
{
   static float t                    = 0;
   video_coord_array_t *ca           = NULL;

   if (!draw)
      return;

   ca                                = &p_disp->dispca;

   draw->x                           = 0;
   draw->y                           = 0;
   draw->coords                      = NULL;
   draw->matrix_data                 = NULL;

   if (ca)
      draw->coords                   = (struct video_coords*)&ca->coords;

   switch (draw->pipeline_id)
   {
      case VIDEO_SHADER_MENU:
      case VIDEO_SHADER_MENU_2:
      case VIDEO_SHADER_MENU_3:
         /* TODO/FIXME - implement */
#if 0
         {
            struct uniform_info uniform_param  = {0};
            t                                 += 0.01;

            uniform_param.enabled              = true;
            uniform_param.lookup.enable        = true;
            uniform_param.lookup.add_prefix    = true;
            uniform_param.lookup.idx           = draw->pipeline_id;
            uniform_param.lookup.type          = SHADER_PROGRAM_VERTEX;
            uniform_param.type                 = UNIFORM_1F;
            uniform_param.lookup.ident         = "time";
            uniform_param.result.f.v0          = t;
         }
#endif
         break;
   }
}

void gfx_display_d3d9_hlsl_scissor_begin(
      void *data,
      unsigned video_width, unsigned video_height,
      int x, int y, unsigned width, unsigned height)
{
   RECT rect;
   d3d9_video_t *d3d9 = (d3d9_video_t*)data;

   if (!d3d9)
      return;

   rect.left          = x;
   rect.top           = y;
   rect.right         = width + x;
   rect.bottom        = height + y;

   IDirect3DDevice9_SetScissorRect(d3d9->dev, &rect);
}

void gfx_display_d3d9_hlsl_scissor_end(void *data,
      unsigned video_width, unsigned video_height)
{
   RECT rect;
   d3d9_video_t   *d3d9 = (d3d9_video_t*)data;

   if (!d3d9)
      return;

   rect.left            = 0;
   rect.top             = 0;
   rect.right           = video_width;
   rect.bottom          = video_height;

   IDirect3DDevice9_SetScissorRect(d3d9->dev, &rect);
}

gfx_display_ctx_driver_t gfx_display_ctx_d3d9_hlsl = {
   gfx_display_d3d9_hlsl_draw,
   gfx_display_d3d9_hlsl_draw_pipeline,
   gfx_display_d3d9_hlsl_blend_begin,
   gfx_display_d3d9_hlsl_blend_end,
   gfx_display_d3d9_hlsl_get_default_mvp,
   gfx_display_d3d9_hlsl_get_default_vertices,
   gfx_display_d3d9_hlsl_get_default_tex_coords,
   FONT_DRIVER_RENDER_D3D9_API,
   GFX_VIDEO_DRIVER_DIRECT3D9_HLSL,
   "d3d9_hlsl",
   false,
   gfx_display_d3d9_hlsl_scissor_begin,
   gfx_display_d3d9_hlsl_scissor_end
};

/*
 * VIDEO DRIVER
 */

static void *d3d9_hlsl_get_constant_by_name(LPD3DXCONSTANTTABLE prog, const char *name)
{
   char lbl[64];
   lbl[0] = '\0';
   snprintf(lbl, sizeof(lbl), "$%s", name);
   return d3d9x_constant_table_get_constant_by_name(prog, NULL, lbl);
}

static INLINE void d3d9_hlsl_set_param_2f(LPD3DXCONSTANTTABLE prog, LPDIRECT3DDEVICE9 userdata, const char *name, const void *values)
{
   D3DXHANDLE param         = (D3DXHANDLE)d3d9_hlsl_get_constant_by_name(prog, name);
   if (param)
      d3d9x_constant_table_set_float_array(userdata, prog, (void*)param, values, 2);
}

static INLINE void d3d9_hlsl_set_param_1f(LPD3DXCONSTANTTABLE prog, LPDIRECT3DDEVICE9 userdata, const char *name, const void *value)
{
   D3DXHANDLE param         = (D3DXHANDLE)d3d9_hlsl_get_constant_by_name(prog, name);
   float *val               = (float*)value;
   if (param)
      d3d9x_constant_table_set_float(prog, userdata, (void*)param, *val);
}

static INLINE void d3d9_hlsl_bind_program(LPDIRECT3DDEVICE9 dev,
      struct shader_pass *pass)
{
   IDirect3DDevice9_SetVertexShader(dev, (LPDIRECT3DVERTEXSHADER9)pass->vprg);
   IDirect3DDevice9_SetPixelShader(dev, (LPDIRECT3DPIXELSHADER9)pass->fprg);
}

static INLINE void d3d9_hlsl_set_param_matrix(LPD3DXCONSTANTTABLE prog, LPDIRECT3DDEVICE9 userdata,
      const char *name, const void *values)
{
   D3DXHANDLE param         = (D3DXHANDLE)d3d9_hlsl_get_constant_by_name(prog, name);
   if (param)
      d3d9x_constant_table_set_matrix(userdata, prog, (void*)param, (D3DMATRIX*)values);
}

static bool d3d9_hlsl_load_program_from_file(
      LPDIRECT3DDEVICE9 dev,
      struct shader_pass *pass,
      const char *prog)
{
   ID3DXBuffer *listing_f                    = NULL;
   ID3DXBuffer *listing_v                    = NULL;
   ID3DXBuffer *code_f                       = NULL;
   ID3DXBuffer *code_v                       = NULL;

   if (string_is_empty(prog))
      return false;

   if (!d3d9x_compile_shader_from_file(prog, NULL, NULL,
            "main_fragment", "ps_3_0", 0, &code_f, &listing_f, &pass->ftable))
   {
      RARCH_ERR("Could not compile fragment shader program (%s)..\n", prog);
      goto error;
   }
   if (!d3d9x_compile_shader_from_file(prog, NULL, NULL,
            "main_vertex", "vs_3_0", 0, &code_v, &listing_v, &pass->vtable))
   {
      RARCH_ERR("Could not compile vertex shader program (%s)..\n", prog);
      goto error;
   }

   IDirect3DDevice9_CreatePixelShader(dev,
         (const DWORD*)code_f->lpVtbl->GetBufferPointer(code_f),
         (LPDIRECT3DPIXELSHADER9*)&pass->fprg);
   IDirect3DDevice9_CreateVertexShader(dev,
         (const DWORD*)code_v->lpVtbl->GetBufferPointer(code_v),
         (LPDIRECT3DVERTEXSHADER9*)&pass->vprg);
   code_f->lpVtbl->Release(code_f);
   code_v->lpVtbl->Release(code_v);

   return true;

error:
   RARCH_ERR("Cg/HLSL error:\n");
   if (listing_f)
   {
      RARCH_ERR("Fragment:\n%s\n", (char*)listing_f->lpVtbl->GetBufferPointer(listing_f));
      listing_f->lpVtbl->Release(listing_f);
   }
   if (listing_v)
   {
      RARCH_ERR("Vertex:\n%s\n", (char*)listing_v->lpVtbl->GetBufferPointer(listing_v));
      listing_v->lpVtbl->Release(listing_v);
   }

   return false;
}

static bool d3d9_hlsl_load_program(
      LPDIRECT3DDEVICE9 dev,
      struct shader_pass *pass,
      const char *prog)
{
   ID3DXBuffer *listing_f = NULL;
   ID3DXBuffer *listing_v = NULL;
   ID3DXBuffer *code_f    = NULL;
   ID3DXBuffer *code_v    = NULL;
   size_t prog_len        = strlen(prog);

   if (!d3d9x_compile_shader(prog, prog_len, NULL, NULL,
            "main_fragment", "ps_3_0", 0, &code_f, &listing_f,
            &pass->ftable ))
   {
      RARCH_ERR("Could not compile stock fragment shader..\n");
      goto error;
   }
   if (!d3d9x_compile_shader(prog, prog_len, NULL, NULL,
            "main_vertex", "vs_3_0", 0, &code_v, &listing_v,
            &pass->vtable ))
   {
      RARCH_ERR("Could not compile stock vertex shader..\n");
      goto error;
   }

   IDirect3DDevice9_CreatePixelShader(dev,
         (const DWORD*)code_f->lpVtbl->GetBufferPointer(code_f),
         (LPDIRECT3DPIXELSHADER9*)&pass->fprg);
   IDirect3DDevice9_CreateVertexShader(dev,
         (const DWORD*)code_v->lpVtbl->GetBufferPointer(code_v),
         (LPDIRECT3DVERTEXSHADER9*)&pass->vprg);
   code_f->lpVtbl->Release(code_f);
   code_v->lpVtbl->Release(code_v);

   return true;

error:
   RARCH_ERR("Cg/HLSL error:\n");
   if (listing_f)
   {
      RARCH_ERR("Fragment:\n%s\n", (char*)listing_f->lpVtbl->GetBufferPointer(listing_f));
      listing_f->lpVtbl->Release(listing_f);
   }
   if (listing_v)
   {
      RARCH_ERR("Vertex:\n%s\n", (char*)listing_v->lpVtbl->GetBufferPointer(listing_v));
      listing_v->lpVtbl->Release(listing_v);
   }
   return false;
}

static void hlsl_d3d9_renderchain_set_shader_params(
      d3d9_renderchain_t *chain,
      LPDIRECT3DDEVICE9 dev,
      struct shader_pass *pass,
      unsigned video_w, unsigned video_h,
      unsigned tex_w, unsigned tex_h,
      unsigned vp_width, unsigned vp_height)
{
   float frame_cnt;
   float video_size[2];
   float texture_size[2];
   float output_size[2];
   LPD3DXCONSTANTTABLE fprg                 = (LPD3DXCONSTANTTABLE)pass->ftable;
   LPD3DXCONSTANTTABLE vprg                 = (LPD3DXCONSTANTTABLE)pass->vtable;

   video_size[0]                            = video_w;
   video_size[1]                            = video_h;
   texture_size[0]                          = tex_w;
   texture_size[1]                          = tex_h;
   output_size[0]                           = vp_width;
   output_size[1]                           = vp_height;

   d3d9x_constant_table_set_defaults(dev, fprg);
   d3d9x_constant_table_set_defaults(dev, vprg);

   d3d9_hlsl_set_param_2f(vprg, dev, "IN.video_size",      &video_size);
   d3d9_hlsl_set_param_2f(fprg, dev, "IN.video_size",      &video_size);
   d3d9_hlsl_set_param_2f(vprg, dev, "IN.texture_size",    &texture_size);
   d3d9_hlsl_set_param_2f(fprg, dev, "IN.texture_size",    &texture_size);
   d3d9_hlsl_set_param_2f(vprg, dev, "IN.output_size",     &output_size);
   d3d9_hlsl_set_param_2f(fprg, dev, "IN.output_size",     &output_size);

   frame_cnt = chain->frame_count;

   if (pass->info.pass->frame_count_mod)
      frame_cnt         = chain->frame_count
         % pass->info.pass->frame_count_mod;

   d3d9_hlsl_set_param_1f(fprg, dev, "IN.frame_count",     &frame_cnt);
   d3d9_hlsl_set_param_1f(vprg, dev, "IN.frame_count",     &frame_cnt);
}

static bool hlsl_d3d9_renderchain_init_shader_fvf(
      d3d9_renderchain_t *chain,
      struct shader_pass *pass)
{
   static const D3DVERTEXELEMENT9 decl[4] =
   {
      {0, offsetof(Vertex, x),  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
         D3DDECLUSAGE_POSITION, 0},
      {0, offsetof(Vertex, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,
         D3DDECLUSAGE_COLOR, 0},
      {0, offsetof(Vertex, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
         D3DDECLUSAGE_TEXCOORD, 0},
      D3DDECL_END()
   };

   return d3d9_vertex_declaration_new(chain->dev,
         decl, (void**)&pass->vertex_decl);
}

static bool hlsl_d3d9_renderchain_create_first_pass(
      LPDIRECT3DDEVICE9 dev,
      d3d9_renderchain_t *chain,
      const struct LinkInfo *info,
      unsigned _fmt)
{
   int i;
   struct shader_pass pass       = { 0 };
   unsigned fmt                  =
        (_fmt == RETRO_PIXEL_FORMAT_RGB565)
      ? D3D9_RGB565_FORMAT
      : D3D9_XRGB8888_FORMAT;

   pass.info                     = *info;
   pass.last_width               = 0;
   pass.last_height              = 0;
   pass.attrib_map               = (struct unsigned_vector_list*)
      unsigned_vector_list_new();

   chain->prev.ptr               = 0;

   for (i = 0; i < TEXTURES; i++)
   {
      int32_t filter             = d3d_translate_filter(info->pass->filter);
      chain->prev.last_width[i]  = 0;
      chain->prev.last_height[i] = 0;
      chain->prev.vertex_buf[i]  = (LPDIRECT3DVERTEXBUFFER9)
         d3d9_vertex_buffer_new(
            chain->dev, 4 * sizeof(struct Vertex),
            D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, NULL);

      if (!chain->prev.vertex_buf[i])
         return false;

      chain->prev.tex[i] = (LPDIRECT3DTEXTURE9)
         d3d9_texture_new(chain->dev,
            info->tex_w, info->tex_h, 1, 0, fmt,
            D3DPOOL_MANAGED, 0, 0, 0, NULL, NULL, false);

      if (!chain->prev.tex[i])
         return false;

      IDirect3DDevice9_SetTexture(chain->dev, 0, (IDirect3DBaseTexture9*)chain->prev.tex[i]);
      IDirect3DDevice9_SetSamplerState(dev,   0, D3DSAMP_MINFILTER, filter);
      IDirect3DDevice9_SetSamplerState(dev,   0, D3DSAMP_MAGFILTER, filter);
      IDirect3DDevice9_SetSamplerState(dev,   0, D3DSAMP_ADDRESSU,  D3DTADDRESS_BORDER);
      IDirect3DDevice9_SetSamplerState(dev,   0, D3DSAMP_ADDRESSV,  D3DTADDRESS_BORDER);
      IDirect3DDevice9_SetTexture(chain->dev, 0, (IDirect3DBaseTexture9*)NULL);
   }

   d3d9_hlsl_load_program_from_file(chain->dev,
         &pass, info->pass->source.path);

   if (!hlsl_d3d9_renderchain_init_shader_fvf(chain, &pass))
      return false;
   shader_pass_vector_list_append(chain->passes, pass);
   return true;
}

static void hlsl_d3d9_renderchain_calc_and_set_shader_mvp(
      hlsl_renderchain_t *chain,
      struct shader_pass *pass,
      unsigned vp_width, unsigned vp_height,
      unsigned rotation)
{
   struct d3d_matrix proj, ortho, rot, matrix;

   d3d_matrix_identity(&ortho);
   d3d_matrix_ortho_off_center_lh(&ortho, 0,
         vp_width, 0, vp_height, 0, 1);
   d3d_matrix_identity(&rot);
   d3d_matrix_rotation_z(&rot, rotation * (D3D_PI / 2.0));
   d3d_matrix_multiply(&proj, &ortho, &rot);
   d3d_matrix_transpose(&matrix, &proj);

   d3d9_hlsl_set_param_matrix((LPD3DXCONSTANTTABLE)pass->vtable,
         chain->chain.dev, "modelViewProj", (const void*)&matrix);
}

static INLINE void d3d9_hlsl_renderchain_set_vertices_on_change(
      d3d9_renderchain_t *chain,
      struct shader_pass *pass,
      unsigned width, unsigned height,
      unsigned out_width, unsigned out_height,
      unsigned vp_width, unsigned vp_height,
      unsigned rotation
      )
{
   struct Vertex vert[4];
   void *verts       = NULL;
   const struct
      LinkInfo *info = (const struct LinkInfo*)&pass->info;
   float _u          = (float)(width)  / info->tex_w;
   float _v          = (float)(height) / info->tex_h;

   pass->last_width  = width;
   pass->last_height = height;

   /* Copied from D3D8 driver */
   vert[0].x        =  0.0f;
   vert[0].y        =  1.0f;
   vert[0].z        =  1.0f;

   vert[1].x        =  1.0f;
   vert[1].y        =  1.0f;
   vert[1].z        =  1.0f;

   vert[2].x        =  0.0f;
   vert[2].y        =  0.0f;
   vert[2].z        =  1.0f;

   vert[3].x        =  1.0f;
   vert[3].y        =  0.0f;
   vert[3].z        =  1.0f;

   vert[0].u        = 0.0f;
   vert[0].v        = 0.0f;
   vert[1].v        = 0.0f;
   vert[2].u        = 0.0f;
   vert[1].u        = _u;
   vert[2].v        = _v;
   vert[3].u        = _u;
   vert[3].v        = _v;

   vert[0].color    = 0xFFFFFFFF;
   vert[1].color    = 0xFFFFFFFF;
   vert[2].color    = 0xFFFFFFFF;
   vert[3].color    = 0xFFFFFFFF;

   /* Align texels and vertices.
    *
    * Fixes infamous 'half-texel offset' issue of D3D9
    *	http://msdn.microsoft.com/en-us/library/bb219690%28VS.85%29.aspx.
    */
   /* Maybe we do need something like this left out for now */
#if 0
   for (i = 0; i < 4; i++)
   {
      vert[i].x    -= 0.5f;
      vert[i].y    += 0.5f;
   }
#endif

   IDirect3DVertexBuffer9_Lock(pass->vertex_buf, 0, 0, &verts, 0);
   memcpy(verts, vert, sizeof(vert));
   IDirect3DVertexBuffer9_Unlock(pass->vertex_buf);
}


static void hlsl_d3d9_renderchain_set_vertices(
      d3d9_video_t *d3d,
      hlsl_renderchain_t *chain,
      struct shader_pass *pass,
      unsigned width, unsigned height,
      unsigned out_width, unsigned out_height,
      unsigned vp_width, unsigned vp_height,
      uint64_t frame_count,
      unsigned rotation)
{
   if (pass->last_width != width || pass->last_height != height)
      d3d9_hlsl_renderchain_set_vertices_on_change(&chain->chain,
            pass, width, height, out_width, out_height,
            vp_width, vp_height, rotation);

   hlsl_d3d9_renderchain_calc_and_set_shader_mvp(chain, pass,
         vp_width, vp_height, rotation);
   hlsl_d3d9_renderchain_set_shader_params(&chain->chain,
         chain->chain.dev,
         pass,
         width, height,
         pass->info.tex_w, pass->info.tex_h,
         vp_width, vp_height);
}

static void d3d9_hlsl_deinit_progs(hlsl_renderchain_t *chain)
{
   if (chain->chain.passes->count >= 1)
   {
      size_t i;

      d3d9_vertex_buffer_free(NULL,
            chain->chain.passes->data[0].vertex_decl);

      for (i = 1; i < chain->chain.passes->count; i++)
      {
         if (chain->chain.passes->data[i].tex)
            IDirect3DTexture9_Release(chain->chain.passes->data[i].tex);
         chain->chain.passes->data[i].tex = NULL;
         d3d9_vertex_buffer_free(
               chain->chain.passes->data[i].vertex_buf,
               chain->chain.passes->data[i].vertex_decl);
      }
   }
}

static void d3d9_hlsl_destroy_resources(hlsl_renderchain_t *chain)
{
   size_t i;

   for (i = 0; i < TEXTURES; i++)
   {
      if (chain->chain.prev.tex[i])
         IDirect3DTexture9_Release(chain->chain.prev.tex[i]);
      if (chain->chain.prev.vertex_buf[i])
         d3d9_vertex_buffer_free(chain->chain.prev.vertex_buf[i], NULL);
   }

   d3d9_hlsl_deinit_progs(chain);

   for (i = 0; i < chain->chain.luts->count; i++)
   {
      if (chain->chain.luts->data[i].tex)
         IDirect3DTexture9_Release(chain->chain.luts->data[i].tex);
   }
}

static void hlsl_d3d9_renderchain_free(void *data)
{
   hlsl_renderchain_t *chain = (hlsl_renderchain_t*)data;

   if (!chain)
      return;

   d3d9_hlsl_destroy_resources(chain);
   d3d9_renderchain_destroy_passes_and_luts(&chain->chain);
   free(chain);
}

static bool hlsl_d3d9_renderchain_init(
      d3d9_video_t *d3d,
      hlsl_renderchain_t *chain,
      LPDIRECT3DDEVICE9 dev,
      const D3DVIEWPORT9 *out_vp,
      const struct LinkInfo *info,
      unsigned fmt
      )
{
   chain->chain.dev                         = dev;
   chain->chain.out_vp                      = (D3DVIEWPORT9*)out_vp;
   chain->chain.frame_count                 = 0;
   chain->chain.pixel_size                  = (fmt == RETRO_PIXEL_FORMAT_RGB565) ? 2 : 4;

   if (!hlsl_d3d9_renderchain_create_first_pass(dev, &chain->chain, info, fmt))
      return false;
   if (!d3d9_hlsl_load_program(chain->chain.dev, &chain->stock_shader, stock_hlsl_program))
      return false;

   d3d9_hlsl_bind_program(dev, &chain->stock_shader);

   return true;
}

static void hlsl_d3d9_renderchain_render_pass(
      hlsl_renderchain_t *chain,
      struct shader_pass *pass,
      unsigned pass_index)
{
   /* Currently we override the passes shader program
      with the stock shader as at least the last pass
      is not setup correctly */
#if 0
   d3d9_hlsl_bind_program(chain->chain.dev, pass);
#else
   d3d9_hlsl_bind_program(chain->chain.dev, &chain->stock_shader);
#endif

   IDirect3DDevice9_SetTexture(chain->chain.dev, 0,
         (IDirect3DBaseTexture9*)pass->tex);

   /* D3D8 sets the sampler address modes -
      I've left them out for the time being
      but maybe this is a bug in d3d9 */
#if 0
   IDirect3DDevice9_SetSamplerState(chain->chain.dev,
         0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
   IDirect3DDevice9_SetSamplerState(chain->chain.dev,
         0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
#endif
   IDirect3DDevice9_SetSamplerState(chain->chain.dev,
         0, D3DSAMP_MINFILTER,
         d3d_translate_filter(pass->info.pass->filter));
   IDirect3DDevice9_SetSamplerState(chain->chain.dev,
         0, D3DSAMP_MAGFILTER,
         d3d_translate_filter(pass->info.pass->filter));

   IDirect3DDevice9_SetVertexDeclaration(
         chain->chain.dev, pass->vertex_decl);
   IDirect3DDevice9_SetStreamSource(
         chain->chain.dev, 0, pass->vertex_buf,
         0,sizeof(struct Vertex));

#if 0
   /* Set orig texture. */
   d3d9_hlsl_renderchain_bind_orig(chain, chain->dev, pass);

   /* Set prev textures. */
   d3d9_hlsl_renderchain_bind_prev(chain, chain->dev, pass);

   /* Set lookup textures */
   for (i = 0; i < chain->luts->count; i++)
   {
      CGparameter vparam;
      CGparameter fparam = d3d9_hlsl_get_constant_by_name(
            pass->fprg, chain->luts->data[i].id);
      int bound_index    = -1;

      if (fparam)
      {
         unsigned index  = cgGetParameterResourceIndex(fparam);
         bound_index     = index;

         d3d9_renderchain_add_lut_internal(chain, index, i);
      }

      vparam = d3d9_hlsl_get_constant_by_name(pass->vprg,
            chain->luts->data[i].id);

      if (vparam)
      {
         unsigned index = cgGetParameterResourceIndex(vparam);
         if (index != (unsigned)bound_index)
            d3d9_renderchain_add_lut_internal(chain, index, i);
      }
   }

   /* We only bother binding passes which are two indices behind. */
   if (pass_index >= 3)
      d3d9_hlsl_renderchain_bind_pass(chain, chain->chain.dev,
            pass, pass_index);
#endif

   IDirect3DDevice9_BeginScene(chain->chain.dev);
   IDirect3DDevice9_DrawPrimitive(chain->chain.dev,
         D3DPT_TRIANGLESTRIP, 0, 2);
   IDirect3DDevice9_EndScene(chain->chain.dev);

   /* So we don't render with linear filter into render targets,
    * which apparently looked odd (too blurry). */
   IDirect3DDevice9_SetSamplerState(chain->chain.dev,
         0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
   IDirect3DDevice9_SetSamplerState(chain->chain.dev,
         0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);

   d3d9_renderchain_unbind_all(&chain->chain);
}

static void hlsl_d3d9_renderchain_render(
      d3d9_video_t *d3d,
      const void *frame,
      unsigned width, unsigned height,
      unsigned pitch, unsigned rotation)
{
   LPDIRECT3DSURFACE9 back_buffer, target;
   unsigned i, current_width, current_height,
          out_width = 0, out_height = 0;
   struct shader_pass *last_pass    = NULL;
   struct shader_pass *first_pass   = NULL;
   hlsl_renderchain_t *chain        = (hlsl_renderchain_t*)
      d3d->renderchain_data;

   d3d9_renderchain_start_render(&chain->chain);

   current_width                  = width;
   current_height                 = height;

   first_pass                     = (struct shader_pass*)
      &chain->chain.passes->data[0];

   d3d9_convert_geometry(
         &first_pass->info,
         &out_width, &out_height,
         current_width, current_height, chain->chain.out_vp);

   d3d9_blit_to_texture(first_pass->tex,
         frame,
         first_pass->info.tex_w,
         first_pass->info.tex_h,
         width,
         height,
         first_pass->last_width,
         first_pass->last_height,
         pitch,
         chain->chain.pixel_size);

   /* Grab back buffer. */
   d3d9_device_get_render_target(
         chain->chain.dev, 0, (void**)&back_buffer);

   /* In-between render target passes. */
   for (i = 0; i < chain->chain.passes->count - 1; i++)
   {
      D3DVIEWPORT9   viewport        = {0};
      struct shader_pass *from_pass  = (struct shader_pass*)
         &chain->chain.passes->data[i];
      struct shader_pass *to_pass    = (struct shader_pass*)
         &chain->chain.passes->data[i + 1];

      IDirect3DTexture9_GetSurfaceLevel(
		      (LPDIRECT3DTEXTURE9)to_pass->tex, 0, (IDirect3DSurface9**)&target);
      IDirect3DDevice9_SetRenderTarget(chain->chain.dev, 0, target);

      d3d9_convert_geometry(&from_pass->info,
            &out_width, &out_height,
            current_width, current_height, chain->chain.out_vp);

      /* Clear out whole FBO. */
      viewport.Width  = to_pass->info.tex_w;
      viewport.Height = to_pass->info.tex_h;
      viewport.MinZ   = 0.0f;
      viewport.MaxZ   = 1.0f;

      IDirect3DDevice9_SetViewport(
            chain->chain.dev, (D3DVIEWPORT9*)&viewport);
      IDirect3DDevice9_Clear(
            chain->chain.dev, 0, 0, D3DCLEAR_TARGET,
            0, 1, 0);

      viewport.Width  = out_width;
      viewport.Height = out_height;

      IDirect3DDevice9_SetViewport(
            chain->chain.dev, (D3DVIEWPORT9*)&viewport);

      hlsl_d3d9_renderchain_set_vertices(
            d3d,
            chain, from_pass,
            current_width, current_height,
            out_width, out_height,
            out_width, out_height,
            chain->chain.frame_count, 0);

      hlsl_d3d9_renderchain_render_pass(chain,
            from_pass,
            i + 1);

      current_width  = out_width;
      current_height = out_height;
      IDirect3DSurface9_Release(target);
   }

   /* Final pass */
   IDirect3DDevice9_SetRenderTarget(chain->chain.dev, 0, back_buffer);

   last_pass = (struct shader_pass*)&chain->chain.passes->
      data[chain->chain.passes->count - 1];

   d3d9_convert_geometry(&last_pass->info,
         &out_width, &out_height,
         current_width, current_height, chain->chain.out_vp);

   IDirect3DDevice9_SetViewport(
         chain->chain.dev, (D3DVIEWPORT9*)chain->chain.out_vp);

   hlsl_d3d9_renderchain_set_vertices(
         d3d,
         chain, last_pass,
         current_width, current_height,
         out_width, out_height,
         chain->chain.out_vp->Width,
         chain->chain.out_vp->Height,
         chain->chain.frame_count, rotation);

   hlsl_d3d9_renderchain_render_pass(chain, last_pass,
         chain->chain.passes->count);

   chain->chain.frame_count++;

   if (back_buffer)
      IDirect3DSurface9_Release(back_buffer);

   d3d9_renderchain_end_render(&chain->chain);
   d3d9_hlsl_bind_program(chain->chain.dev, &chain->stock_shader);
   hlsl_d3d9_renderchain_calc_and_set_shader_mvp(
         chain, &chain->stock_shader,
         chain->chain.out_vp->Width,
         chain->chain.out_vp->Height, 0);
}

static bool hlsl_d3d9_renderchain_add_pass(
      hlsl_renderchain_t *chain,
      const struct LinkInfo *info)
{
   struct shader_pass pass;

   pass.info                   = *info;
   pass.last_width             = 0;
   pass.last_height            = 0;
   pass.attrib_map             = (struct unsigned_vector_list*)
      unsigned_vector_list_new();
   pass.pool                   = D3DPOOL_DEFAULT;

   d3d9_hlsl_load_program_from_file(
         chain->chain.dev, &pass, info->pass->source.path);

   if (hlsl_d3d9_renderchain_init_shader_fvf(&chain->chain, &pass))
      return d3d9_renderchain_add_pass(&chain->chain, &pass,
            info);
   return false;
}

/* END HLSL RENDERCHAIN */

static uint32_t d3d9_hlsl_get_flags(void *data)
{
   uint32_t flags = 0;

   BIT32_SET(flags, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION);
   BIT32_SET(flags, GFX_CTX_FLAGS_MENU_FRAME_FILTERING);
   BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_HLSL);

   return flags;
}

static void d3d9_hlsl_deinitialize(d3d9_video_t *d3d)
{
   font_driver_free_osd();

   hlsl_d3d9_renderchain_free(d3d->renderchain_data);

   d3d9_vertex_buffer_free(d3d->menu_display.buffer,
         d3d->menu_display.decl);

   d3d->renderchain_data    = NULL;
   d3d->menu_display.buffer = NULL;
   d3d->menu_display.decl   = NULL;
}

static bool d3d9_hlsl_init_base(
      d3d9_video_t *d3d, const video_info_t *info)
{
   D3DPRESENT_PARAMETERS d3dpp;
#ifndef _XBOX
   HWND focus_window  = win32_get_window();
#endif

   memset(&d3dpp, 0, sizeof(d3dpp));

   g_pD3D9            = (LPDIRECT3D9)d3d9_create();

   /* this needs g_pD3D9 created first */
   d3d9_make_d3dpp(d3d, info, &d3dpp);

   if (!g_pD3D9)
      return false;
   if (!d3d9_create_device(&d3d->dev, &d3dpp,
            g_pD3D9,
            focus_window,
            d3d->cur_mon_id)
      )
      return false;
   return true;
}

static bool renderchain_d3d_hlsl_init_first(
      enum gfx_ctx_api api,
      void **renderchain_handle)
{
   hlsl_renderchain_t *renderchain =
      (hlsl_renderchain_t*)calloc(1, sizeof(*renderchain));
   if (!renderchain)
      return false;

   d3d9_init_renderchain(&renderchain->chain);

   *renderchain_handle = renderchain;

   return true;
}

static bool d3d9_hlsl_init_chain(d3d9_video_t *d3d,
      unsigned input_scale, bool rgb32)
{
   struct LinkInfo link_info;
#ifndef _XBOX
   unsigned current_width, current_height, out_width, out_height;
#endif
   unsigned i           = 0;
   settings_t *settings = config_get_ptr();
   bool video_smooth    = settings->bools.video_smooth;

   /* Setup information for first pass. */
   link_info.pass       = NULL;
   link_info.tex_w      = input_scale * RARCH_SCALE_BASE;
   link_info.tex_h      = input_scale * RARCH_SCALE_BASE;
   link_info.pass       = &d3d->shader.pass[0];

   if (!renderchain_d3d_hlsl_init_first(GFX_CTX_DIRECT3D9_API,
            &d3d->renderchain_data))
      return false;
   if (!d3d->renderchain_data)
      return false;

   RARCH_LOG("[D3D9]: Using HLSL shader backend.\n");

   if (
         !hlsl_d3d9_renderchain_init(
            d3d, (hlsl_renderchain_t*)d3d->renderchain_data,
            d3d->dev, &d3d->out_vp, &link_info,
            rgb32
            ? RETRO_PIXEL_FORMAT_XRGB8888
            : RETRO_PIXEL_FORMAT_RGB565
            )
      )
      return false;

   d3d9_log_info(&link_info);

#ifndef _XBOX
   current_width  = link_info.tex_w;
   current_height = link_info.tex_h;
   out_width      = 0;
   out_height     = 0;

   for (i = 1; i < d3d->shader.passes; i++)
   {
      d3d9_convert_geometry(
            &link_info,
            &out_width, &out_height,
            current_width, current_height, &d3d->out_vp);

      link_info.pass  = &d3d->shader.pass[i];
      link_info.tex_w = next_pow2(out_width);
      link_info.tex_h = next_pow2(out_height);

      current_width   = out_width;
      current_height  = out_height;

      if (!hlsl_d3d9_renderchain_add_pass(
               (hlsl_renderchain_t*)d3d->renderchain_data, &link_info))
      {
         RARCH_ERR("[D3D9]: Failed to add pass.\n");
         return false;
      }
      d3d9_log_info(&link_info);
   }
#endif

   {
      hlsl_renderchain_t *_chain = (hlsl_renderchain_t*)d3d->renderchain_data;
      d3d9_renderchain_t *chain   = (d3d9_renderchain_t*)&_chain->chain;

      for (i = 0; i < d3d->shader.luts; i++)
      {
         if (!d3d9_renderchain_add_lut(
                  chain,
                  d3d->shader.lut[i].id,
                  d3d->shader.lut[i].path,
                  d3d->shader.lut[i].filter == RARCH_FILTER_UNSPEC
                  ? video_smooth
                  : (d3d->shader.lut[i].filter == RARCH_FILTER_LINEAR)))
         {
            RARCH_ERR("[D3D9]: Failed to init LUTs.\n");
            return false;
         }
      }
   }

   return true;
}

static bool d3d9_hlsl_initialize(
      d3d9_video_t *d3d, const video_info_t *info)
{
   unsigned width, height;
   bool ret             = true;

   if (!g_pD3D9)
      ret               = d3d9_hlsl_init_base(d3d, info);
   else if (d3d->needs_restore)
   {
      D3DPRESENT_PARAMETERS d3dpp;

      d3d9_make_d3dpp(d3d, info, &d3dpp);

      /* The D3DX font driver uses POOL_DEFAULT resources
       * and will prevent a clean reset here
       * another approach would be to keep track of all created D3D
       * font objects and free/realloc them around the d3d_reset call  */
#ifdef HAVE_MENU
      menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL);
#endif

      if (!d3d9_reset(d3d->dev, &d3dpp))
      {
         d3d9_hlsl_deinitialize(d3d);
         IDirect3D9_Release(g_pD3D9);
         g_pD3D9 = NULL;

         ret     = d3d9_hlsl_init_base(d3d, info);
         if (ret)
            RARCH_LOG("[D3D9]: Recovered from dead state.\n");
      }

#ifdef HAVE_MENU
      menu_driver_init(info->is_threaded);
#endif
   }

   if (!ret)
      return ret;

   if (!d3d9_hlsl_init_chain(d3d, info->input_scale, info->rgb32))
   {
      RARCH_ERR("[D3D9]: Failed to initialize render chain.\n");
      return false;
   }

   video_driver_get_size(&width, &height);
   d3d9_set_viewport(d3d,
      width, height, false, true);

   font_driver_init_osd(d3d, info,
         false,
         info->is_threaded,
         FONT_DRIVER_RENDER_D3D9_API);

   {
      static const D3DVERTEXELEMENT9 VertexElements[4] = {
         {0, offsetof(Vertex, x),
            D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT,
            D3DDECLUSAGE_POSITION,0},
         {0, offsetof(Vertex, u),
            D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT,
            D3DDECLUSAGE_TEXCOORD,0},
         {0, offsetof(Vertex, color),
            D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,
            D3DDECLUSAGE_COLOR,   0},
         D3DDECL_END()
      };
      if (!d3d9_vertex_declaration_new(d3d->dev,
               (void*)VertexElements, (void**)&d3d->menu_display.decl))
         return false;
   }

   d3d->menu_display.offset = 0;
   d3d->menu_display.size   = 1024;
   d3d->menu_display.buffer = d3d9_vertex_buffer_new(
         d3d->dev, d3d->menu_display.size * sizeof(Vertex),
         D3DUSAGE_WRITEONLY,
         0,
         D3DPOOL_DEFAULT,
         NULL);

   if (!d3d->menu_display.buffer)
      return false;

   d3d_matrix_identity(&d3d->mvp_transposed);
   d3d_matrix_ortho_off_center_lh(&d3d->mvp_transposed, 0, 1, 0, 1, 0, 1);
   d3d->mvp = d3d->mvp_transposed;

   if (d3d->translate_x)
   {
      struct d3d_matrix *pout = (struct d3d_matrix*)&d3d->mvp;
      float vp_x = -(d3d->translate_x/(float)d3d->out_vp.Width);
      pout->m[3][0] = -1.0f + vp_x - 2.0f * 1 / (0 - 1);
   }

   if (d3d->translate_y)
   {
      struct d3d_matrix *pout = (struct d3d_matrix*)&d3d->mvp;
      float vp_y = -(d3d->translate_y/(float)d3d->out_vp.Height);
      pout->m[3][1] = 1.0f + vp_y + 2.0f * 1 / (0 - 1);
   }

   IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_CULLMODE, D3DCULL_NONE);
   IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_SCISSORTESTENABLE, TRUE);

   return true;
}

static bool d3d9_hlsl_restore(d3d9_video_t *d3d)
{
   d3d9_hlsl_deinitialize(d3d);

   if (!d3d9_hlsl_initialize(d3d, &d3d->video_info))
   {
      RARCH_ERR("[D3D9]: Restore error.\n");
      return false;
   }

   d3d->needs_restore = false;

   return true;
}

static bool d3d9_hlsl_set_shader(void *data,
      enum rarch_shader_type type, const char *path)
{
   d3d9_video_t *d3d = (d3d9_video_t*)data;

   if (!d3d)
      return false;

   if (!string_is_empty(d3d->shader_path))
      free(d3d->shader_path);
   d3d->shader_path = NULL;

   switch (type)
   {
      case RARCH_SHADER_CG:
      case RARCH_SHADER_HLSL:
         if (!string_is_empty(path))
            d3d->shader_path = strdup(path);

         break;
      case RARCH_SHADER_NONE:
         break;
      default:
         RARCH_WARN("[D3D9]: Only Cg shaders are supported. Falling back to stock.\n");
   }

   if (!d3d9_process_shader(d3d) || !d3d9_hlsl_restore(d3d))
   {
      RARCH_ERR("[D3D9]: Failed to set shader.\n");
      return false;
   }

   return true;
}

static bool d3d9_hlsl_init_internal(d3d9_video_t *d3d,
      const video_info_t *info, input_driver_t **input,
      void **input_data)
{
#ifdef HAVE_MONITOR
   bool windowed_full;
   RECT mon_rect;
   MONITORINFOEX current_mon;
   HMONITOR hm_to_use;
#endif
#ifdef HAVE_WINDOW
   DWORD style;
   unsigned win_width        = 0;
   unsigned win_height       = 0;
   RECT rect                 = {0};
#endif
   unsigned full_x           = 0;
   unsigned full_y           = 0;
   settings_t    *settings   = config_get_ptr();
   overlay_t *menu           = (overlay_t*)calloc(1, sizeof(*menu));

   if (!menu)
      return false;

   d3d->menu                 = menu;
   d3d->cur_mon_id           = 0;
   d3d->menu->tex_coords[0]  = 0;
   d3d->menu->tex_coords[1]  = 0;
   d3d->menu->tex_coords[2]  = 1;
   d3d->menu->tex_coords[3]  = 1;
   d3d->menu->vert_coords[0] = 0;
   d3d->menu->vert_coords[1] = 1;
   d3d->menu->vert_coords[2] = 1;
   d3d->menu->vert_coords[3] = -1;

#ifdef HAVE_WINDOW
   memset(&d3d->windowClass, 0, sizeof(d3d->windowClass));
   d3d->windowClass.lpfnWndProc = wnd_proc_d3d_common;
#ifdef HAVE_DINPUT
   if (string_is_equal(settings->arrays.input_driver, "dinput"))
      d3d->windowClass.lpfnWndProc = wnd_proc_d3d_dinput;
#endif
#ifdef HAVE_WINRAWINPUT
   if (string_is_equal(settings->arrays.input_driver, "raw"))
      d3d->windowClass.lpfnWndProc = wnd_proc_d3d_winraw;
#endif
   win32_window_init(&d3d->windowClass, true, NULL);
#endif

#ifdef HAVE_MONITOR
   win32_monitor_info(&current_mon, &hm_to_use, &d3d->cur_mon_id);

   mon_rect              = current_mon.rcMonitor;
   g_win32_resize_width  = info->width;
   g_win32_resize_height = info->height;

   windowed_full         = settings->bools.video_windowed_fullscreen;

   full_x                = (windowed_full || info->width  == 0)
      ? (unsigned)(mon_rect.right  - mon_rect.left)
      : info->width;
   full_y                = (windowed_full || info->height == 0)
      ? (unsigned)(mon_rect.bottom - mon_rect.top)
      : info->height;
#else
   d3d9_get_video_size(d3d, &full_x, &full_y);
#endif
   {
      unsigned new_width  = info->fullscreen ? full_x : info->width;
      unsigned new_height = info->fullscreen ? full_y : info->height;
      video_driver_set_size(new_width, new_height);
   }

#ifdef HAVE_WINDOW
   video_driver_get_size(&win_width, &win_height);

   win32_set_style(&current_mon, &hm_to_use, &win_width, &win_height,
         info->fullscreen, windowed_full, &rect, &mon_rect, &style);

   win32_window_create(d3d, style, &mon_rect, win_width,
         win_height, info->fullscreen);

   win32_set_window(&win_width, &win_height, info->fullscreen,
      windowed_full, &rect);
#endif

   d3d->video_info = *info;

   if (!d3d9_hlsl_initialize(d3d, &d3d->video_info))
      return false;

   d3d9_hlsl_fake_context.get_flags   = d3d9_hlsl_get_flags;
#ifndef _XBOX_
   d3d9_hlsl_fake_context.get_metrics = win32_get_metrics;
#endif
   video_context_driver_set(&d3d9_hlsl_fake_context);
   {
      const char *shader_preset   = video_shader_get_current_shader_preset();
      enum rarch_shader_type type = video_shader_parse_type(shader_preset);

      d3d9_hlsl_set_shader(d3d, type, shader_preset);
   }

   d3d_input_driver(settings->arrays.input_joypad_driver,
      settings->arrays.input_joypad_driver, input, input_data);

   {
      char version_str[128];
      D3DADAPTER_IDENTIFIER9 ident = {0};

      IDirect3D9_GetAdapterIdentifier(g_pD3D9, 0, 0, &ident);

      version_str[0] = '\0';

      snprintf(version_str, sizeof(version_str), "%u.%u.%u.%u",
            HIWORD(ident.DriverVersion.HighPart),
            LOWORD(ident.DriverVersion.HighPart),
            HIWORD(ident.DriverVersion.LowPart),
            LOWORD(ident.DriverVersion.LowPart));

      RARCH_LOG("[D3D9]: Using GPU: \"%s\".\n", ident.Description);
      RARCH_LOG("[D3D9]: GPU API Version: %s\n", version_str);
      video_driver_set_gpu_api_version_string(version_str);
   }

   return true;
}

static void *d3d9_hlsl_init(const video_info_t *info,
      input_driver_t **input, void **input_data)
{
   d3d9_video_t *d3d = (d3d9_video_t*)calloc(1, sizeof(*d3d));

   if (!d3d)
      return NULL;
   if (!d3d9_initialize_symbols(GFX_CTX_DIRECT3D9_API))
      goto error;

#ifndef _XBOX
   win32_window_reset();
   win32_monitor_init();
#endif

   /* Default values */
   d3d->dev                  = NULL;
   d3d->dev_rotation         = 0;
   d3d->needs_restore        = false;
#ifdef HAVE_OVERLAY
   d3d->overlays_enabled     = false;
#endif
   d3d->should_resize        = false;
   d3d->menu                 = NULL;

   if (!d3d9_hlsl_init_internal(d3d, info, input, input_data))
      goto error;

   d3d->keep_aspect       = info->force_aspect;

   return d3d;

error:
   RARCH_ERR("[D3D9]: Failed to init D3D.\n");
   free(d3d);
   return NULL;
}

static void d3d9_hlsl_free(void *data)
{
   d3d9_video_t   *d3d = (d3d9_video_t*)data;

   if (!d3d)
      return;

#ifdef HAVE_OVERLAY
   d3d9_free_overlays(d3d);
   if (d3d->overlays)
      free(d3d->overlays);
   d3d->overlays      = NULL;
   d3d->overlays_size = 0;
#endif

   d3d9_free_overlay(d3d, d3d->menu);
   if (d3d->menu)
      free(d3d->menu);
   d3d->menu          = NULL;

   d3d9_hlsl_deinitialize(d3d);

   if (!string_is_empty(d3d->shader_path))
      free(d3d->shader_path);

   IDirect3DDevice9_Release(d3d->dev);
   IDirect3D9_Release(g_pD3D9);
   d3d->shader_path = NULL;
   d3d->dev         = NULL;
   g_pD3D9          = NULL;

   d3d9_deinitialize_symbols();

#ifndef _XBOX
   win32_monitor_from_window();
   win32_destroy_window();
#endif
   free(d3d);
}

static bool d3d9_hlsl_frame(void *data, const void *frame,
      unsigned frame_width, unsigned frame_height,
      uint64_t frame_count, unsigned pitch,
      const char *msg, video_frame_info_t *video_info)
{
   D3DVIEWPORT9 screen_vp;
   unsigned i                          = 0;
   d3d9_video_t *d3d                   = (d3d9_video_t*)data;
   unsigned width                      = video_info->width;
   unsigned height                     = video_info->height;
   bool statistics_show                = video_info->statistics_show;
   unsigned black_frame_insertion      = video_info->black_frame_insertion;
   struct font_params *osd_params      = (struct font_params*)
      &video_info->osd_stat_params;
   const char *stat_text               = video_info->stat_text;
#ifdef HAVE_MENU
   bool menu_is_alive                  = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false;
#else
   bool menu_is_alive                  = false;
#endif
   bool overlay_behind_menu            = video_info->overlay_behind_menu;
#ifdef HAVE_GFX_WIDGETS
   bool widgets_active                 = video_info->widgets_active;
#endif

   if (!frame)
      return true;

   /* We cannot recover in fullscreen. */
   if (d3d->needs_restore)
   {
#ifndef _XBOX
      HWND window = win32_get_window();
      if (IsIconic(window))
         return true;
#endif

      if (!d3d9_hlsl_restore(d3d))
      {
         RARCH_ERR("[D3D9]: Failed to restore.\n");
         return false;
      }
   }

   if (d3d->should_resize)
   {
      hlsl_renderchain_t *_chain = (hlsl_renderchain_t*)d3d->renderchain_data;
      d3d9_renderchain_t *chain  = (d3d9_renderchain_t*)&_chain->chain;

      d3d9_set_viewport(d3d, width, height, false, true);

      if (chain)
         chain->out_vp           = (D3DVIEWPORT9*)&d3d->out_vp;

      d3d9_recompute_pass_sizes(chain->dev, chain, d3d);

      d3d->should_resize         = false;
   }

   /* render_chain() only clears out viewport,
    * clear out everything. */
   screen_vp.X      = 0;
   screen_vp.Y      = 0;
   screen_vp.MinZ   = 0;
   screen_vp.MaxZ   = 1;
   screen_vp.Width  = width;
   screen_vp.Height = height;
   IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
   IDirect3DDevice9_Clear(d3d->dev, 0, 0, D3DCLEAR_TARGET,
         0, 1, 0);

   IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
         (const float*)&d3d->mvp, 4);
   hlsl_d3d9_renderchain_render(
         d3d, frame, frame_width, frame_height,
         pitch, d3d->dev_rotation);

   if (black_frame_insertion && !d3d->menu->enabled)
   {
      unsigned n;
      for (n = 0; n < video_info->black_frame_insertion; ++n)
      {
#ifdef _XBOX
        bool ret = true;
        IDirect3DDevice9_Present(d3d->dev, NULL, NULL, NULL, NULL);
#else
        bool ret = (IDirect3DDevice9_Present(d3d->dev,
                 NULL, NULL, NULL, NULL) != D3DERR_DEVICELOST);
#endif
        if (!ret || d3d->needs_restore)
          return true;
        IDirect3DDevice9_Clear(d3d->dev, 0, 0, D3DCLEAR_TARGET,
              0, 1, 0);
      }
   }

#ifdef HAVE_OVERLAY
   if (d3d->overlays_enabled && overlay_behind_menu)
   {
      IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
            (const float*)&d3d->mvp_transposed, 4);
      for (i = 0; i < d3d->overlays_size; i++)
         d3d9_overlay_render(d3d, width, height, &d3d->overlays[i], true);
   }
#endif

#ifdef HAVE_MENU
   if (d3d->menu && d3d->menu->enabled)
   {
      IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
            (const float*)&d3d->mvp, 4);
      d3d9_overlay_render(d3d, width, height, d3d->menu, false);

      d3d->menu_display.offset = 0;
      IDirect3DDevice9_SetVertexDeclaration(d3d->dev, (LPDIRECT3DVERTEXDECLARATION9)d3d->menu_display.decl);
      IDirect3DDevice9_SetStreamSource(d3d->dev, 0,
            (LPDIRECT3DVERTEXBUFFER9)d3d->menu_display.buffer,
            0, sizeof(Vertex));
      IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
      menu_driver_frame(menu_is_alive, video_info);
   }
   else if (statistics_show)
   {
      if (osd_params)
      {
         IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
         IDirect3DDevice9_BeginScene(d3d->dev);
         font_driver_render_msg(d3d, stat_text,
               (const struct font_params*)osd_params, NULL);
         IDirect3DDevice9_EndScene(d3d->dev);
      }
   }
#endif

#ifdef HAVE_OVERLAY
   if (d3d->overlays_enabled && !overlay_behind_menu)
   {
      IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
            (const float*)&d3d->mvp_transposed, 4);
      for (i = 0; i < d3d->overlays_size; i++)
         d3d9_overlay_render(d3d, width, height, &d3d->overlays[i], true);
   }
#endif

#ifdef HAVE_GFX_WIDGETS
   if (widgets_active)
      gfx_widgets_frame(video_info);
#endif

   if (msg && *msg)
   {
      IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
      IDirect3DDevice9_BeginScene(d3d->dev);
      font_driver_render_msg(d3d, msg, NULL, NULL);
      IDirect3DDevice9_EndScene(d3d->dev);
   }

   video_driver_update_title(NULL);
   IDirect3DDevice9_Present(d3d->dev, NULL, NULL, NULL, NULL);

   return true;
}

static const video_poke_interface_t d3d9_hlsl_poke_interface = {
   d3d9_hlsl_get_flags,
   d3d9_load_texture,
   d3d9_unload_texture,
   d3d9_set_video_mode,
#if defined(_XBOX) || defined(__WINRT__)
   NULL, /* get_refresh_rate */
#else
   /* UWP does not expose this information easily */
   win32_get_refresh_rate,
#endif
   NULL, /* set_filtering */
   NULL, /* get_video_output_size */
   NULL, /* get_video_output_prev */
   NULL, /* get_video_output_next */
   NULL, /* get_current_framebuffer */
   NULL, /* get_proc_address */
   d3d9_set_aspect_ratio,
   d3d9_apply_state_changes,
   d3d9_set_menu_texture_frame,
   d3d9_set_menu_texture_enable,
   d3d9_set_osd_msg,
   win32_show_cursor,
   NULL, /* grab_mouse_toggle */
   NULL, /* get_current_shader */
   NULL, /* get_current_software_framebuffer */
   NULL, /* get_hw_render_interface */
   NULL, /* set_hdr_max_nits */
   NULL, /* set_hdr_paper_white_nits */
   NULL, /* set_hdr_contrast */
   NULL  /* set_hdr_expand_gamut */
};

static void d3d9_hlsl_get_poke_interface(void *data,
      const video_poke_interface_t **iface)
{
   *iface = &d3d9_hlsl_poke_interface;
}

#ifdef HAVE_GFX_WIDGETS
static bool d3d9_hlsl_gfx_widgets_enabled(void *data)
{
   return false; /* currently disabled due to memory issues */
}
#endif

static void d3d9_hlsl_set_resize(d3d9_video_t *d3d,
      unsigned new_width, unsigned new_height)
{
   /* No changes? */
   if (     (new_width  == d3d->video_info.width)
         && (new_height == d3d->video_info.height))
      return;

   d3d->video_info.width  = new_width;
   d3d->video_info.height = new_height;
   video_driver_set_size(new_width, new_height);
}

static bool d3d9_hlsl_alive(void *data)
{
   unsigned temp_width   = 0;
   unsigned temp_height  = 0;
   bool ret              = false;
   bool        quit      = false;
   bool        resize    = false;
   d3d9_video_t *d3d     = (d3d9_video_t*)data;

   /* Needed because some context drivers don't track their sizes */
   video_driver_get_size(&temp_width, &temp_height);

   win32_check_window(NULL, &quit, &resize, &temp_width, &temp_height);

   if (quit)
      d3d->quitting      = quit;

   if (resize)
   {
      d3d->should_resize = true;
      d3d9_hlsl_set_resize(d3d, temp_width, temp_height);
      d3d9_hlsl_restore(d3d);
   }

   ret = !quit;

   if (  temp_width  != 0 &&
         temp_height != 0)
      video_driver_set_size(temp_width, temp_height);

   return ret;
}

static void d3d9_hlsl_set_nonblock_state(void *data, bool state,
      bool adaptive_vsync_enabled,
      unsigned swap_interval)
{
#ifdef _XBOX
   int interval          = 0;
#endif
   d3d9_video_t     *d3d = (d3d9_video_t*)data;

   if (!d3d)
      return;

#ifdef _XBOX
   if (!state)
      interval           = 1;
#endif

   d3d->video_info.vsync = !state;

#ifdef _XBOX
   IDirect3DDevice9_SetRenderState(d3d->dev,
         D3DRS_PRESENTINTERVAL,
         interval ?
         D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE);
#else
   d3d->needs_restore    = true;
   d3d9_hlsl_restore(d3d);
#endif
}

#ifdef _XBOX
static bool d3d9_hlsl_suspend_screensaver(void *data, bool enable) { return true; }
#endif

video_driver_t video_d3d9_hlsl = {
   d3d9_hlsl_init,
   d3d9_hlsl_frame,
   d3d9_hlsl_set_nonblock_state,
   d3d9_hlsl_alive,
   NULL, /* focus */
#ifdef _XBOX
   d3d9_hlsl_suspend_screensaver,
#else
   win32_suspend_screensaver,
#endif
   d3d9_has_windowed,
   d3d9_hlsl_set_shader,
   d3d9_hlsl_free,
   "d3d9_hlsl",
   d3d9_set_viewport,
   d3d9_set_rotation,
   d3d9_viewport_info,
   d3d9_read_viewport,
   NULL, /* read_frame_raw */
#ifdef HAVE_OVERLAY
   d3d9_get_overlay_interface,
#endif
   d3d9_hlsl_get_poke_interface,
   NULL, /* wrap_type_to_enum */
#ifdef HAVE_GFX_WIDGETS
   d3d9_hlsl_gfx_widgets_enabled
#endif
};
