name: "Outside" panels { name: "FOOT" path: "Panels/panel_1" clue: "foot" answer: "yard" symbols: BOXES required_door { name: "Outside Puzzles" } } panels { name: "SEVERE" path: "Panels/panel_2" clue: "severe" answer: "grave" symbols: SUN required_door { name: "Outside Puzzles" } } ad>
summary refs log blame commit diff stats
path: root/src/renderer.cpp
blob: c2b37d23fdea6c85a322c97e25ea86a2454594ba (plain) (tree)
1
2
3
4
5
6
7
8
9
10




                     
                  
                  
                 

                                       





                                




                                        


                                                     

                               
 



                                                    

                           







                                             
                          

                            
 





                                      





                                
































                                                                                
                                                                      





                                                                      
                                                                                             




                                                                    
                                                                          





                                                                        
                                                                                                 












                                                                             
                                                                                    







                                                     












                                                                                               

 


                                                                                                                                               
                      



















                                                               
                       



                                                                
                   



                                             
                       
















                                                                                                                                                                              




                                                             

                                               
  


                                                       

                                

                                                         
                                                                            
                                                                                                     

                                    

                                      








                                                                                        










                                                                                            





                                                                      





















                                                                           

                                                                         













                                                    

                                                              
                               


                                       
                         


                          



                                                    
                           

                                                         


                                                 






                                                         
                                                                        
                                                                                                     
  


















                                                                                                  








                                                                                    










                                                                                        




                                                                      

             









                                                                            
                                                                                                             


                                               
                                                                                                   


                                                   
                                                                                                           
  













                                                                                                              













                                                                                                     
                                                                                               





                                                                                                     





                                                                                    

                                                                                       













                                                  

                                         


                                         
  




                               

                                





                                      
                                    

                                      

                           


                                                
  


                                          





                              
                                       






                                                  

                        
  

                                      
                                                                                          



                                                                      

 
                                      






                                                  









                                                                                             

 
                                    






                                                  









                                                                                             

                                                                    
                                                                      
                                                                      
  
             

 





                                               






                                                  





                                         
  







                                       

 
                                                          







                                                  
                                                         
                                                                       

                                 



                                                                           















                                                                                              
                                  
                               








                                                                                             
                                                                                             






                                                  

                                      
                           
                                                         
                                                                       

                                 



                                                                           













                                                                                              



                                                                         















                                                                                                  
                               
                                  

                               
                                             
                                                             
                                                                










                                        
                                                                                                  










                                                                        
                                    















                                                                                
                                  






                                                  

                                                                     
                                                         

                                                                                         
                      
                                         
                                                     

                           
                                                                        
                               
                                      





                                                                          
                                                                                   







                                                                          
                                   


                                                                          

                                                                          







                                                                      
                           
                               
                                                   


                                                               
 
                                         

                                                                             
                                             
                                                     
                            

                                                                                            










                                                                                       
                              



                                                                    
                                



                                                                                                           


                                                                                                  
                                                                                            
                                                                               
  
                               
                                                   


                                                               
                                                   

                                                               





                                                               


                                
                         
                                                                 

                                                                                          


                                                     
                                                
                                                     



                                            
                                                                 

                               

                                                                








                                                                                

                          

                            
 
                                   


                               
#include "renderer.h"
#include <string>
#include <fstream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include "game.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

// include stb_image
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_ONLY_BMP
#include "stb_image.h"

static bool rendererInitialized = false;

static GLFWwindow* window;

static GLuint generic_framebuffer; // The framebuffer
static GLuint bloom_framebuffer;
static GLuint bloom_depthbuffer;
static int buffer_width = 1024;
static int buffer_height = 768;

static GLuint ntscShader; // The NTSC shader
static GLuint finalShader; // The passthrough shader
static GLuint blitShader; // The blitting shader
static GLuint fillShader; // The fill shader
static GLuint bloom1Shader;
static GLuint bloom2Shader;

// The buffers for the NTSC rendering process
static GLuint renderedTex1;
static GLuint renderedTex2;
static GLuint renderedTexBufs[2];
static int curBuf;
static GLuint artifactsTex;
static GLuint scanlinesTex;
static GLuint preBloomTex;
static GLuint bloomPassTex1;
static GLuint bloomPassTex2;

// The VAO
static GLuint VertexArrayID;

// A plane that fills the renderbuffer
static GLuint quad_vertexbuffer;

// Buffers for the mesh
static GLuint mesh_vertexbuffer;
static GLuint mesh_uvbuffer;
static GLuint mesh_normalbuffer;
static int mesh_numvertices;

GLuint LoadShaders(const char* vertex_file_path, const char* fragment_file_path)
{ 
    // Create the shaders
    GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
 
    // Read the Vertex Shader code from the file
    std::string VertexShaderCode;
    std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
    if(VertexShaderStream.is_open())
    {
        std::string Line = "";
        while(getline(VertexShaderStream, Line))
            VertexShaderCode += "\n" + Line;
        VertexShaderStream.close();
    }
 
    // Read the Fragment Shader code from the file
    std::string FragmentShaderCode;
    std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
    if(FragmentShaderStream.is_open()){
        std::string Line = "";
        while(getline(FragmentShaderStream, Line))
            FragmentShaderCode += "\n" + Line;
        FragmentShaderStream.close();
    }
 
    GLint Result = GL_FALSE;
    int InfoLogLength;
 
    // Compile Vertex Shader
    printf("Compiling shader : %s\n", vertex_file_path);
    char const * VertexSourcePointer = VertexShaderCode.c_str();
    glShaderSource(VertexShaderID, 1, &VertexSourcePointer , nullptr);
    glCompileShader(VertexShaderID);
 
    // Check Vertex Shader
    glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> VertexShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(VertexShaderID, InfoLogLength, nullptr, &VertexShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);
 
    // Compile Fragment Shader
    printf("Compiling shader : %s\n", fragment_file_path);
    char const * FragmentSourcePointer = FragmentShaderCode.c_str();
    glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , nullptr);
    glCompileShader(FragmentShaderID);
 
    // Check Fragment Shader
    glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> FragmentShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(FragmentShaderID, InfoLogLength, nullptr, &FragmentShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);
 
    // Link the program
    fprintf(stdout, "Linking program\n");
    GLuint ProgramID = glCreateProgram();
    glAttachShader(ProgramID, VertexShaderID);
    glAttachShader(ProgramID, FragmentShaderID);
    glLinkProgram(ProgramID);
 
    // Check the program
    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> ProgramErrorMessage( glm::max(InfoLogLength, int(1)) );
    glGetProgramInfoLog(ProgramID, InfoLogLength, nullptr, &ProgramErrorMessage[0]);
    fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);
 
    glDeleteShader(VertexShaderID);
    glDeleteShader(FragmentShaderID);
 
    return ProgramID;
}

void flipImageData(unsigned char* data, int width, int height, int comps)
{
  unsigned char* data_copy = (unsigned char*) malloc(width*height*comps*sizeof(unsigned char));
  memcpy(data_copy, data, width*height*comps);
  
  int row_size = width * comps;
  
  for (int i=0;i<height;i++)
  {
    memcpy(data + (row_size*i), data_copy + (row_size*(height-i-1)), row_size);
  }
  
  free(data_copy);
}

void loadMesh(const char* filename, std::vector<glm::vec3>& out_vertices, std::vector<glm::vec2>& out_uvs, std::vector<glm::vec3>& out_normals)
{
  FILE* file = fopen(filename, "r");
  if (file == nullptr)
  {
    fprintf(stderr, "Could not open mesh file %s\n", filename);
    exit(1);
  }
  
  std::vector<glm::vec3> temp_vertices;
  std::vector<glm::vec2> temp_uvs;
  std::vector<glm::vec3> temp_normals;
  
  for (;;)
  {
    char lineHeader[256];
    int res = fscanf(file, "%s", lineHeader);
    if (res == EOF)
    {
      break;
    }
    
    if (!strncmp(lineHeader, "v", 2))
    {
      glm::vec3 vertex;
      fscanf(file, "%f %f %f\n", &vertex.x,&vertex.y,&vertex.z);
      temp_vertices.push_back(vertex);
    } else if (!strncmp(lineHeader, "vt", 3))
    {
      glm::vec2 uv;
      fscanf(file, "%f %f\n", &uv.x, &uv.y);
      temp_uvs.push_back(uv);
    } else if (!strncmp(lineHeader, "vn", 3))
    {
      glm::vec3 normal;
      fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z);
      temp_normals.push_back(normal);
    } else if (!strncmp(lineHeader, "f", 2))
    {
      int vertexIDs[3], uvIDs[3], normalIDs[3];
      fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIDs[0], &uvIDs[0], &normalIDs[0], &vertexIDs[1], &uvIDs[1], &normalIDs[1], &vertexIDs[2], &uvIDs[2], &normalIDs[2]);
      
      for (int i=0; i<3; i++)
      {
        out_vertices.push_back(temp_vertices[vertexIDs[i] - 1]);
        out_uvs.push_back(temp_uvs[uvIDs[i] - 1]);
        out_normals.push_back(temp_normals[normalIDs[i] - 1]);
      }
    }
  }
}

void setFramebufferSize(GLFWwindow* w, int width, int height)
{
  buffer_width = width;
  buffer_height = height;
  
  glDeleteFramebuffers(1, &bloom_framebuffer);
  glDeleteRenderbuffers(1, &bloom_depthbuffer);
  
  glGenFramebuffers(1, &bloom_framebuffer);
  glBindFramebuffer(GL_FRAMEBUFFER, bloom_framebuffer);
  GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT1};
  glDrawBuffers(1, DrawBuffers);
  
  glGenRenderbuffers(1, &bloom_depthbuffer);
  glBindRenderbuffer(GL_RENDERBUFFER, bloom_depthbuffer);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, bloom_depthbuffer);
  
  glDeleteTextures(1, &preBloomTex);
  glDeleteTextures(1, &bloomPassTex1);
  glDeleteTextures(1, &bloomPassTex2);
  
  glGenTextures(1, &preBloomTex);
  glBindTexture(GL_TEXTURE_2D, preBloomTex);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  glGenTextures(1, &bloomPassTex1);
  glBindTexture(GL_TEXTURE_2D, bloomPassTex1);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width/4, height/4, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  glGenTextures(1, &bloomPassTex2);
  glBindTexture(GL_TEXTURE_2D, bloomPassTex2);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width/4, height/4, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

GLFWwindow* initRenderer()
{
  if (rendererInitialized)
  {
    fprintf(stderr, "Renderer already initialized\n");
    exit(-1);
  }
  
  // Initialize GLFW
  if (!glfwInit())
  {
    fprintf(stderr, "Failed to initialize GLFW\n");
    exit(-1);
  }
  
  glfwWindowHint(GLFW_SAMPLES, 4); // 4x antialiasing
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // We want version 3.3
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Mac requires this
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  
  // Create a window
  window = glfwCreateWindow(1024, 768, "Aromatherapy", nullptr, nullptr);
  if (window == nullptr)
  {
    fprintf(stderr, "Failed to open GLFW window\n");
    glfwTerminate();
    exit(-1);
  }
  
  glfwMakeContextCurrent(window);
  glewExperimental = true; // Needed in core profile
  if (glewInit() != GLEW_OK)
  {
    fprintf(stderr, "Failed to initialize GLEW\n");
    exit(-1);
  }
  
  glfwSetFramebufferSizeCallback(window, &setFramebufferSize);
  
  // Set up vertex array object
  glGenVertexArrays(1, &VertexArrayID);
  glBindVertexArray(VertexArrayID);
  
  // Enable depth testing
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
  
  // Enable blending
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  
  // Set up the framebuffer
  glGenFramebuffers(1, &generic_framebuffer);
  glBindFramebuffer(GL_FRAMEBUFFER, generic_framebuffer);
  GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
  glDrawBuffers(1, DrawBuffers);
  
  glGenFramebuffers(1, &bloom_framebuffer);
  glBindFramebuffer(GL_FRAMEBUFFER, bloom_framebuffer);
  GLenum DrawBuffers2[1] = {GL_COLOR_ATTACHMENT1};
  glDrawBuffers(1, DrawBuffers2);
  
  glGenRenderbuffers(1, &bloom_depthbuffer);
  glBindRenderbuffer(GL_RENDERBUFFER, bloom_depthbuffer);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 1024, 768);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, bloom_depthbuffer);
  
  // Set up the NTSC rendering buffers
  glGenTextures(1, &renderedTex1);
  glBindTexture(GL_TEXTURE_2D, renderedTex1);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, GAME_WIDTH, GAME_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  renderedTexBufs[0] = renderedTex1;
  
  glGenTextures(1, &renderedTex2);
  glBindTexture(GL_TEXTURE_2D, renderedTex2);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, GAME_WIDTH, GAME_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  renderedTexBufs[1] = renderedTex2;
  
  // Set up bloom rendering buffers
  glGenTextures(1, &preBloomTex);
  glBindTexture(GL_TEXTURE_2D, preBloomTex);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1024, 768, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  glGenTextures(1, &bloomPassTex1);
  glBindTexture(GL_TEXTURE_2D, bloomPassTex1);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1024/4, 768/4, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  glGenTextures(1, &bloomPassTex2);
  glBindTexture(GL_TEXTURE_2D, bloomPassTex2);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1024/4, 768/4, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  curBuf = 0;
  
  // Load the mesh!
  std::vector<glm::vec3> mesh_vertices;
  std::vector<glm::vec2> mesh_uvs;
  std::vector<glm::vec3> mesh_normals;
  loadMesh("../res/monitor-fef.obj", mesh_vertices, mesh_uvs, mesh_normals);
  
  mesh_numvertices = mesh_vertices.size();
  
  glGenBuffers(1, &mesh_vertexbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, mesh_vertexbuffer);
  glBufferData(GL_ARRAY_BUFFER, mesh_vertices.size() * sizeof(glm::vec3), &mesh_vertices[0], GL_STATIC_DRAW);
  
  glGenBuffers(1, &mesh_uvbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, mesh_uvbuffer);
  glBufferData(GL_ARRAY_BUFFER, mesh_uvs.size() * sizeof(glm::vec3), &mesh_uvs[0], GL_STATIC_DRAW);
  
  glGenBuffers(1, &mesh_normalbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, mesh_normalbuffer);
  glBufferData(GL_ARRAY_BUFFER, mesh_normals.size() * sizeof(glm::vec3), &mesh_normals[0], GL_STATIC_DRAW);
  
  // Load the vertices of a flat surface
  GLfloat g_quad_vertex_buffer_data[] = { 
		-1.0f, -1.0f, 0.0f,
		 1.0f, -1.0f, 0.0f,
		-1.0f,  1.0f, 0.0f,
		-1.0f,  1.0f, 0.0f,
		 1.0f, -1.0f, 0.0f,
		 1.0f,  1.0f, 0.0f,
	};

  glGenBuffers(1, &quad_vertexbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
  glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW);
  
  glGenTextures(1, &artifactsTex);
  glBindTexture(GL_TEXTURE_2D, artifactsTex);
  int atdw, atdh;
  unsigned char* artifactsTex_data = stbi_load("../res/artifacts.bmp", &atdw, &atdh, 0, 3);
  flipImageData(artifactsTex_data, atdw, atdh, 3);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, atdw, atdh, 0, GL_RGB, GL_UNSIGNED_BYTE, artifactsTex_data);
  stbi_image_free(artifactsTex_data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
  glGenerateMipmap(GL_TEXTURE_2D);
  
  glGenTextures(1, &scanlinesTex);
  glBindTexture(GL_TEXTURE_2D, scanlinesTex);
  int stdw, stdh;
  unsigned char* scanlinesTex_data = stbi_load("../res/scanlines_333.bmp", &stdw, &stdh, 0, 3);
  flipImageData(scanlinesTex_data, stdw, stdh, 3);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, atdw, atdh, 0, GL_RGB, GL_UNSIGNED_BYTE, scanlinesTex_data);
  stbi_image_free(scanlinesTex_data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
  glGenerateMipmap(GL_TEXTURE_2D);
  
  // Load the shaders
  ntscShader = LoadShaders("../shaders/ntsc.vertex", "../shaders/ntsc.fragment");
  finalShader = LoadShaders("../shaders/final.vertex", "../shaders/final.fragment");
  blitShader = LoadShaders("../shaders/blit.vertex", "../shaders/blit.fragment");
  fillShader = LoadShaders("../shaders/fill.vertex", "../shaders/fill.fragment");
  bloom1Shader = LoadShaders("../shaders/bloom1.vertex", "../shaders/bloom1.fragment");
  bloom2Shader = LoadShaders("../shaders/bloom2.vertex", "../shaders/bloom2.fragment");
  
  rendererInitialized = true;
  
  return window;
}

void destroyRenderer()
{
  if (!rendererInitialized)
  {
    fprintf(stderr, "Renderer not initialized\n");
    exit(-1);
  }
  
  // Delete the plane buffer
  glDeleteBuffers(1, &quad_vertexbuffer);
  glDeleteBuffers(1, &mesh_vertexbuffer);
  glDeleteBuffers(1, &mesh_uvbuffer);
  glDeleteBuffers(1, &mesh_normalbuffer);
  
  // Delete the shaders
  glDeleteProgram(ntscShader);
  glDeleteProgram(finalShader);
  glDeleteProgram(blitShader);
  glDeleteProgram(fillShader);
  glDeleteProgram(bloom1Shader);
  glDeleteProgram(bloom2Shader);
  
  // Delete the NTSC rendering buffers
  glDeleteTextures(1, &renderedTex1);
  glDeleteTextures(1, &renderedTex2);
  glDeleteTextures(1, &artifactsTex);
  glDeleteTextures(1, &scanlinesTex);
  glDeleteTextures(1, &preBloomTex);
  glDeleteTextures(1, &bloomPassTex1);
  glDeleteTextures(1, &bloomPassTex2);
  
  // Delete the framebuffer
  glDeleteRenderbuffers(1, &bloom_depthbuffer);
  glDeleteFramebuffers(1, &bloom_framebuffer);
  glDeleteFramebuffers(1, &generic_framebuffer);
  
  // Delete the VAO
  glDeleteVertexArrays(1, &VertexArrayID);
  
  // Kill the window
  glfwTerminate();
  
  rendererInitialized = false;
}

Texture::Texture(int width, int height)
{
  if (!rendererInitialized)
  {
    fprintf(stderr, "Renderer not initialized\n");
    exit(-1);
  }
  
  this->width = width;
  this->height = height;
  
  glGenTextures(1, &texID);
  glBindTexture(GL_TEXTURE_2D, texID);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
}

Texture::Texture(const char* filename)
{
  if (!rendererInitialized)
  {
    fprintf(stderr, "Renderer not initialized\n");
    exit(-1);
  }
  
  glGenTextures(1, &texID);
  glBindTexture(GL_TEXTURE_2D, texID);
  unsigned char* data = stbi_load(filename, &width, &height, 0, 4);
  flipImageData(data, width, height, 4);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
  stbi_image_free(data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

Texture::Texture(const Texture& tex)
{
  if (!rendererInitialized)
  {
    fprintf(stderr, "Renderer not initialized\n");
    exit(-1);
  }
  
  width = tex.width;
  height = tex.height;
  
  unsigned char* data = (unsigned char*) malloc(4 * width * height);
  glBindTexture(GL_TEXTURE_2D, tex.texID);
  glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
  
  glGenTextures(1, &texID);
  glBindTexture(GL_TEXTURE_2D, texID);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  
  free(data);
}

Texture::Texture(Texture&& tex) : Texture(0, 0)
{
  swap(*this, tex);
}

Texture::~Texture()
{
  if (!rendererInitialized)
  {
    fprintf(stderr, "Renderer not initialized\n");
    exit(-1);
  }
  
  glDeleteTextures(1, &texID);
}

Texture& Texture::operator= (Texture tex)
{
  swap(*this, tex);
  
  return *this;
}

void swap(Texture& tex1, Texture& tex2)
{
  std::swap(tex1.width, tex2.width);
  std::swap(tex1.height, tex2.height);
  std::swap(tex1.texID, tex2.texID);
}

void Texture::fill(Rectangle dstrect, int r, int g, int b)
{
  if (!rendererInitialized)
  {
    fprintf(stderr, "Renderer not initialized\n");
    exit(-1);
  }
  
  // Target the framebuffer
  glBindFramebuffer(GL_FRAMEBUFFER, generic_framebuffer);
  glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texID, 0);
  
  // Set up the vertex attributes
  GLfloat minx = (GLfloat) dstrect.x / width * 2.0 - 1.0;
  GLfloat miny = -((GLfloat) dstrect.y / height * 2.0 - 1.0);
  GLfloat maxx = (GLfloat) (dstrect.x + dstrect.w) / width * 2.0 - 1.0;
  GLfloat maxy = -((GLfloat) (dstrect.y + dstrect.h) / height * 2.0 - 1.0);
  
  GLfloat vertexbuffer_data[] = {
    minx, miny,
    maxx, miny,
    maxx, maxy,
    minx, miny,
    minx, maxy,
    maxx, maxy
  };
  GLuint vertexbuffer;
  glGenBuffers(1, &vertexbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertexbuffer_data), vertexbuffer_data, GL_STATIC_DRAW);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
  
  glViewport(0, 0, width, height);
  glClear(GL_DEPTH_BUFFER_BIT);
  glUseProgram(fillShader);
  glUniform3f(glGetUniformLocation(fillShader, "vecColor"), r / 255.0, g / 255.0, b / 255.0);
  
  glDrawArrays(GL_TRIANGLES, 0, 6);
  
  glDisableVertexAttribArray(0);
  glDeleteBuffers(1, &vertexbuffer);
}

void Texture::blit(const Texture& srctex, Rectangle srcrect, Rectangle dstrect, double alpha)
{
  if (!rendererInitialized)
  {
    fprintf(stderr, "Renderer not initialized\n");
    exit(-1);
  }
  
  alpha = glm::clamp(alpha, 0.0, 1.0);
  
  // Target the framebuffer
  glBindFramebuffer(GL_FRAMEBUFFER, generic_framebuffer);
  glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texID, 0);
  
  // Set up the vertex attributes
  GLfloat minx = (GLfloat) dstrect.x / width * 2.0 - 1.0;
  GLfloat miny = -((GLfloat) dstrect.y / height * 2.0 - 1.0);
  GLfloat maxx = (GLfloat) (dstrect.x + dstrect.w) / width * 2.0 - 1.0;
  GLfloat maxy = -((GLfloat) (dstrect.y + dstrect.h) / height * 2.0 - 1.0);
  
  GLfloat vertexbuffer_data[] = {
    minx, miny,
    maxx, miny,
    minx, maxy,
    maxx, maxy
  };
  GLuint vertexbuffer;
  glGenBuffers(1, &vertexbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertexbuffer_data), vertexbuffer_data, GL_STATIC_DRAW);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
  
  GLfloat minu = (GLfloat) srcrect.x / srctex.width;
  GLfloat minv = 1 - ((GLfloat) srcrect.y / srctex.height);
  GLfloat maxu = (GLfloat) (srcrect.x + srcrect.w) / srctex.width;
  GLfloat maxv = 1 - ((GLfloat) (srcrect.y + srcrect.h) / srctex.height);
  
  GLfloat texcoordbuffer_data[] = {
    minu, minv,
    maxu, minv,
    minu, maxv,
    maxu, maxv
  };
  GLuint texcoordbuffer;
  glGenBuffers(1, &texcoordbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, texcoordbuffer);
  glBufferData(GL_ARRAY_BUFFER, sizeof(texcoordbuffer_data), texcoordbuffer_data, GL_STATIC_DRAW);
  glEnableVertexAttribArray(1);
  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
  
  // Set up the shader
  glUseProgram(blitShader);
  glClear(GL_DEPTH_BUFFER_BIT);
  glViewport(0, 0, width, height);
  
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, srctex.texID);
  glUniform1i(glGetUniformLocation(blitShader, "srctex"), 0);
  glUniform1f(glGetUniformLocation(blitShader, "alpha"), alpha);
  
  // Blit!
  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
  
  // Unload everything
  glDisableVertexAttribArray(1);
  glDisableVertexAttribArray(0);
  glDeleteBuffers(1, &texcoordbuffer);
  glDeleteBuffers(1, &vertexbuffer);
}

void bloomPass1(GLuint srcTex, GLuint dstTex, bool horizontal, glm::vec2 srcRes, glm::vec2 dstRes)
{
  glBindFramebuffer(GL_FRAMEBUFFER, generic_framebuffer);
  glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, dstTex, 0);
  glViewport(0,0,dstRes.x,dstRes.y);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glUseProgram(bloom1Shader);
  
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, srcTex);
  glUniform1i(glGetUniformLocation(bloom1Shader, "inTex"), 0);
  
  glm::vec2 offset = glm::vec2(0.0);
  if (horizontal)
  {
    offset.x = 1.2/srcRes.x;
  } else {
    offset.y = 1.2/srcRes.y;
  }
  
  glUniform2f(glGetUniformLocation(bloom1Shader, "offset"), offset.x, offset.y);
  
  glEnableVertexAttribArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
  glDrawArrays(GL_TRIANGLES, 0, 6);
  glDisableVertexAttribArray(0);
}

void Texture::renderScreen() const
{
  if (!rendererInitialized)
  {
    fprintf(stderr, "Renderer not initialized\n");
    exit(-1);
  }
  
  // First we're going to composite our frame with the previous frame
  // We start by setting up the framebuffer
  glBindFramebuffer(GL_FRAMEBUFFER, generic_framebuffer);
  glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexBufs[curBuf], 0);
  
  // Set up the shader
  glViewport(0,0,GAME_WIDTH,GAME_HEIGHT);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glUseProgram(ntscShader);
  
  // Use the current frame texture, nearest neighbor and clamped to edge
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, texID);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glUniform1i(glGetUniformLocation(ntscShader, "curFrameSampler"), 0);
  
  // Use the previous frame composite texture, nearest neighbor and clamped to edge
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, renderedTexBufs[(curBuf + 1) % 2]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glUniform1i(glGetUniformLocation(ntscShader, "prevFrameSampler"), 1);
  
  // Load the NTSC artifact texture
  glActiveTexture(GL_TEXTURE2);
  glBindTexture(GL_TEXTURE_2D, artifactsTex);
  glUniform1i(glGetUniformLocation(ntscShader, "NTSCArtifactSampler"), 2);
  glUniform1f(glGetUniformLocation(ntscShader, "NTSCLerp"), curBuf * 1.0);
  
  if ((rand() % 60) == 0)
  {
    // Change the 0.0 to a 1.0 or a 10.0 for a glitchy effect!
    glUniform1f(glGetUniformLocation(ntscShader, "Tuning_NTSC"), 0.0);
  } else {
    glUniform1f(glGetUniformLocation(ntscShader, "Tuning_NTSC"), 0.0);
  }
  
  // Render our composition
  glEnableVertexAttribArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
  glDrawArrays(GL_TRIANGLES, 0, 6);
  glDisableVertexAttribArray(0);

  // We're going to render the screen now
  glBindFramebuffer(GL_FRAMEBUFFER, bloom_framebuffer);
  glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, preBloomTex, 0);
  glViewport(0,0,buffer_width,buffer_height);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glUseProgram(finalShader);
  
  // Use the composited frame texture, linearly filtered and filling in black for the border
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, renderedTexBufs[curBuf]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
  float border_color[] = {0.0f, 0.0f, 0.0f, 1.0f};
  glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
	glGenerateMipmap(GL_TEXTURE_2D);
  glUniform1i(glGetUniformLocation(finalShader, "rendertex"), 0);
  
  // Use the scanlines texture
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, scanlinesTex);
  glUniform1i(glGetUniformLocation(finalShader, "scanlinestex"), 1);
  
  // Initialize the MVP matrices
  glm::mat4 p_matrix = glm::perspective(42.5f, (float) buffer_width / (float) buffer_height, 0.1f, 100.0f);
  glm::mat4 v_matrix = glm::lookAt(glm::vec3(2,0,0), glm::vec3(0,0,0), glm::vec3(0,1,0));
  glm::mat4 m_matrix = glm::mat4(1.0);
  glm::mat4 mvp_matrix = p_matrix * v_matrix * m_matrix;
  
  glUniformMatrix4fv(glGetUniformLocation(finalShader, "MVP"), 1, GL_FALSE, &mvp_matrix[0][0]);
  glUniformMatrix4fv(glGetUniformLocation(finalShader, "worldMat"), 1, GL_FALSE, &m_matrix[0][0]);
  glUniform2f(glGetUniformLocation(finalShader, "resolution"), buffer_width, buffer_height);
  glUniform1f(glGetUniformLocation(finalShader, "iGlobalTime"), glfwGetTime());
  
  glEnableVertexAttribArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, mesh_vertexbuffer);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
  
  glEnableVertexAttribArray(1);
  glBindBuffer(GL_ARRAY_BUFFER, mesh_normalbuffer);
  glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
  
  glEnableVertexAttribArray(2);
  glBindBuffer(GL_ARRAY_BUFFER, mesh_uvbuffer);
  glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
  
  glDrawArrays(GL_TRIANGLES, 0, mesh_numvertices);
  glDisableVertexAttribArray(2);
  glDisableVertexAttribArray(1);
  glDisableVertexAttribArray(0);
  
  // First pass of bloom!
  glm::vec2 buffer_size = glm::vec2(buffer_width, buffer_height);
  bloomPass1(preBloomTex, bloomPassTex1, true, buffer_size, buffer_size / 4.0f);
  bloomPass1(bloomPassTex1, bloomPassTex2, false, buffer_size / 4.0f, buffer_size / 4.0f);
  
  // Do the second pass of bloom and render to screen
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
  glViewport(0, 0, buffer_width, buffer_height);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glUseProgram(bloom2Shader);
  
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, preBloomTex);
  glUniform1i(glGetUniformLocation(bloom2Shader, "clearTex"), 0);
  
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, bloomPassTex2);
  glUniform1i(glGetUniformLocation(bloom2Shader, "blurTex"), 1);
  
  glUniform1f(glGetUniformLocation(bloom2Shader, "iGlobalTime"), glfwGetTime());
  
  glEnableVertexAttribArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
  glDrawArrays(GL_TRIANGLES, 0, 6);
  glDisableVertexAttribArray(0);
  
  glfwSwapBuffers(window);
  
  curBuf = (curBuf + 1) % 2;
}

Rectangle Texture::entirety() const
{
  return {0, 0, width, height};
}