Portfolio

Code Samples

Post Processing Framework

After hardcoding several Post Processing Effects for various things I started to find the common requirements between post process effects and set out to create an easy to use framework. I decided that I wanted the effects completely defined in the .fx files and settled on using annotations.

Brief Sample Effect
The below example defines a post processing effect that takes two input textures one from file and another from a previous render target. It outputs to two render targets and if the render targets don't exist it creates them as a 32bit ARGB texture.

technique main
{
  pass P0
  <
  int    RenderTargetCount = 2;
  float2 RenderTargetSize  = float2(1,1);
  string RenderTarget0     = "diffuse";
  string RenderTarget1     = "normal";
  int    RenderTargetFMT0  = RT_U8ARGB;
  int    RenderTargetFMT1  = RT_U8ARGB;

  int    InputTextureCount = 2;
  int    TextureType0      = TT_TEXTURE;
  int    TextureType1      = TT_RENDERTARGET;
  string TextureName0      = "neon_metal.dds";
  string TextureName1      = "position";
  >
  {
    vertexShader = compile vs_3_0 PostProcVS();
    pixelShader  = compile ps_3_0 PostProcPS();

    AlphaBlendEnable = false;
    AlphaTestEnable  = false;
    ZEnable          = false;
    ZWriteEnable     = false;

    ColorWriteEnable = red | green | blue | alpha;
  }
}


Header File
For defining a Post Processing Effect in C++ I create a few layers. The first layer is an individual pass which stores the name of the needed render targets and a reference to any necessary textures. The second layer is the set of passes that get iterated over. When selecting textures the effect has three options: From File, Render Targets or Noise.

/* All content Copyright 2010 DigiPen (USA) Corporation, all rights reserved. **
** File:    Graphics/PostProcessEffect.h
** Author:  Matthew Sorenson
** Email:   msorenso@digipen.edu
** Brief:
**
*******************************************************************************/

#ifndef GRAPHICS_POSTPROCESSINGEFFECT_H
#define GRAPHICS_POSTPROCESSINGEFFECT_H

#include <string>
#include <vector>

#include "../GUT/Device.h"
#include "../GUT/Shader.h"
#include "../GUT/Texture.h"

#include "View.h"

namespace Graphics{
  class ComponentFactory;

  // Everything in the PostProcHelpers namespace are used internally by the
  // PostProcessEffect class
  namespace PostProcHelpers{
    struct TargetDescripter
    {
      std::string    name;
      GUT::RT_Format format;
    };

    enum TextureType { TT_TEXTURE = 0, TT_RENDERTARGET = 1, TT_NOISE = 2 };

    struct TextureDescriptor
    {
      std::string       name;
      GUT::Texture::Ptr texture;
      TextureType       type;
    };

    typedef std::vector< TargetDescripter  > TargetDescripterList;
    typedef std::vector< TextureDescriptor > TextureDescriptorList;

    struct Pass
    {
      float2                render_target_size;
      TargetDescripterList  target_list;
      TextureDescriptorList texture_list;
    };
  }

  class PostProcessEffect
  {
  public:
    PostProcessEffect(GUT::Device* device, const std::string& filename);
    ~PostProcessEffect();

    void Render(View& view);

  private:
    typedef PostProcHelpers::Pass Pass;
    typedef std::vector< Pass >   PassList;

    // These functions handle loading and parsing the annotations
    void    LoadFile(const std::string& filename);
    GHandle GetAnnotation(GHandle handle, const std::string& name);
    void    InitializeTargets( GHandle passh, Pass& pass);
    void    InitializeTextures(GHandle passh, Pass& pass);

    // These functions are helpers for settings up individual passes for
    // rendering
    void BeginPass(Pass& pass, View& view, int pass_num);
    void EndPass();

    void SetPassTargets( Pass& pass, int targetID,  View& view);
    void SetPassTextures(Pass& pass, int textureID, View& view);

    GUT::Device*       m_device;
    GUT::Shader::Ptr   m_shader;
    GUT::Mesh*         m_screen_quad;

    GUT::RenderTarget* m_current_targets[4];

    GHandle            m_tech;
    GHandle            m_screen_size;

    PassList           m_passes;
  };
}

#endif //GRAPHICS_POSTPROCESSINGEFFECT_H

Source File
Most of the grunt work for running the effects is in the SetPassTargets and SetPassTextures functions which ensure that each pass has the resources it needs. Most of the loading takes place in the InitializeTextures and InitializeTargets which determine what each pass requires.

/* All content Copyright 2010 DigiPen (USA) Corporation, all rights reserved. **
** File:    Graphics/PostProcessEffect.cpp
** Author:  Matthew Sorenson
** Email:   msorenso@digipen.edu
** Brief:
**
*******************************************************************************/

#include "PostProcessEffect.h"

#include "../GUT/RenderTarget.h"

#include "ComponentFactory.h"

namespace Graphics{
  PostProcessEffect::PostProcessEffect(
    GUT::Device*       device,
    const std::string& filename)
  : m_device(device)
  {
    LOG_ASSERT_NOT_NULL(device);
    LoadFile(filename);

    m_screen_quad = GUT::CreateFullScreenQuad(m_device);

    for(int i = 0; i < 4; ++i)
      m_current_targets[i] = NULL;
  }

  PostProcessEffect::~PostProcessEffect()
  {
    SAFE_DELETE(m_screen_quad);
  }

  void PostProcessEffect::Render(View& view)
  {
    m_device->SetIndexBuffer(m_screen_quad->GetIndexBuffer());
    m_device->SetVertexBuffer(m_screen_quad->GetVertexBuffer());

    float2 resolution = float2(m_device->GetSettings().resolution);
    m_shader->SetFloat2(m_screen_size, resolution);

    m_shader->SetTechnique(m_shader->GetTechniqueByName("main"));
    m_shader->Begin();
    for(int i = 0; i < (int)m_passes.size(); ++i){
      BeginPass(m_passes[i], view, i);

      m_device->DrawIndexed();

      EndPass();
    }
    m_shader->End();

    m_device->SetVertexBuffer(NULL);
    m_device->SetIndexBuffer(NULL);
  }

  void PostProcessEffect::BeginPass(Pass& pass, View& view, int pass_num)
  {
    for(int i = 0; i < (int)pass.target_list.size() && i < 4; ++i){
      SetPassTargets(pass, i, view);
    }
    m_device->Clear(GUT::CLEAR_COLOR);

    for(int i = 0; i < (int)pass.texture_list.size(); ++i){
      SetPassTextures(pass, i, view);
    }
    m_shader->BeginPass(pass_num);
  }

  void PostProcessEffect::EndPass()
  {
    m_shader->EndPass();
    for(int i = 0; i < 4; ++i){
      if(m_current_targets[i]){
        m_current_targets[i]->End();
        m_current_targets[i] = NULL;
      }
    }
  }

  void PostProcessEffect::SetPassTargets(Pass& pass,int targetID, View& view)
  {
    const std::string& name = pass.target_list[targetID].name;
    GUT::RT_Format format   = pass.target_list[targetID].format;

    GUT::RenderTarget* rt = view.GetRenderTarget(name);

    if(NULL == rt){
      rt = m_device->GetRenderTarget(pass.render_target_size, format);

      LogErrorIf(NULL == rt, "Failed to create Render Target %s", name.c_str());
      view.AddRenderTarget(name, rt);
    }

    LogErrorIf(NULL == rt, "Render Target %s not fount", name.c_str());
    m_current_targets[targetID] = rt;
    m_current_targets[targetID]->Begin(targetID);
  }

  void PostProcessEffect::SetPassTextures(Pass& pass, int targetID, View& view)
  {
    PostProcHelpers::TextureDescriptor texture = pass.texture_list[targetID];

    char name[MAX_PATH];
    sprintf_s(name, MAX_PATH, "tex%i", targetID);
    GHandle texHandle = m_shader->GetParameterByName(name);

    switch(texture.type){
      case PostProcHelpers::TT_TEXTURE:
      case PostProcHelpers::TT_NOISE:
        m_shader->SetTexture(texHandle, texture.texture);
        break;
      case PostProcHelpers::TT_RENDERTARGET:{
        GUT::RenderTarget* rt = view.GetRenderTarget(texture.name);
        LogErrorIf(!rt, "Could not find RenderTarget %s", texture.name.c_str());
        m_shader->SetTexture(texHandle, rt);
        break;
      }
      default:
        LogError("Undefined Texture Type for Post Processing Effect %s",
          m_shader->GetFileName().c_str());
        break;
    }
  }

  void PostProcessEffect::LoadFile(const std::string& filename)
  {
    m_shader      = m_device->GetShader(filename);
    m_tech        = m_shader->GetTechniqueByName("main");
    m_screen_size = m_shader->GetParameterByName("screenSize");

    LOG_ASSERT_NOT_NULL(m_tech);
    LOG_ASSERT_NOT_NULL(m_screen_size);

    int passCount = m_shader->GetPassCount(m_tech);
    for(int currPass = 0; currPass < passCount; ++currPass){
      Pass    pass;
      GHandle passh  = m_shader->GetPass(m_tech, currPass);

      InitializeTargets(passh, pass);
      InitializeTextures(passh, pass);

      m_passes.push_back(pass);
    }
  }

  GHandle PostProcessEffect::GetAnnotation(
    GHandle handle,
    const std::string& name)
  {
    GHandle tmp = m_shader->GetAnnotationByName(handle, name.c_str());
    LOG_ASSERT_NOT_NULL(tmp);
    return tmp;
  }

  void PostProcessEffect::InitializeTargets(GHandle passh, Pass& pass)
  {
    char tmp[MAX_PATH];
    GHandle handle = m_shader->GetAnnotationByName(passh, "RenderTargetSize");
    if(handle)
      m_shader->GetFloat2(handle, pass.render_target_size);
    else
      pass.render_target_size = float2(1, 1);

    int rtCount;
    m_shader->GetInt(GetAnnotation(passh, "RenderTargetCount"), rtCount);
    for(int i = 0; i < rtCount; ++i){
      PostProcHelpers::TargetDescripter desc;
      sprintf(tmp, "RenderTarget%i", i);
      m_shader->GetString(GetAnnotation(passh, tmp), desc.name);

      int fmt;
      sprintf(tmp, "RenderTargetFMT%i", i);
      m_shader->GetInt(GetAnnotation(passh, tmp), fmt);
      desc.format = GUT::RT_Format(fmt);

      pass.target_list.push_back(desc);
    }
  }

  void PostProcessEffect::InitializeTextures(GHandle passh, Pass& pass)
  {
    char tmp[MAX_PATH];
    int  tCount;
    m_shader->GetInt(GetAnnotation(passh,"InputTextureCount"), tCount);

    for(int i = 0; i < tCount; ++i){
      PostProcHelpers::TextureDescriptor desc;
      sprintf(tmp, "TextureName%i", i);
      m_shader->GetString(GetAnnotation(passh, tmp), desc.name);

      sprintf(tmp, "TextureType%i", i);
      int type;
      m_shader->GetInt(GetAnnotation(passh, tmp), type);
      desc.type = PostProcHelpers::TextureType(type);

      switch(desc.type){
        case PostProcHelpers::TT_NOISE:
          desc.name = "noise.dds";
        case PostProcHelpers::TT_TEXTURE:
          desc.texture = m_device->GetTexture(desc.name);
          break;
        default:
          break;
      }

      pass.texture_list.push_back(desc);
    }
  }
}