From 68cc99065092c2d2f968c0243eef1d0d78a6cf20 Mon Sep 17 00:00:00 2001
From: Abdelrahman Said <said.abdelrahman89@gmail.com>
Date: Fri, 27 Dec 2024 22:22:32 +0000
Subject: [PATCH] Implement double frame buffering

---
 shaders/pp_frag.glsl | 18 ++++++++
 shaders/pp_vert.glsl | 12 ++++++
 src/main.cc          | 98 ++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 124 insertions(+), 4 deletions(-)
 create mode 100644 shaders/pp_frag.glsl
 create mode 100644 shaders/pp_vert.glsl

diff --git a/shaders/pp_frag.glsl b/shaders/pp_frag.glsl
new file mode 100644
index 0000000..c9de194
--- /dev/null
+++ b/shaders/pp_frag.glsl
@@ -0,0 +1,18 @@
+#version 330 core
+
+struct Material {
+  sampler2D diffuse1;
+  sampler2D specular1;
+  float shininess;
+};
+
+in vec2 uv_coords;
+
+uniform Material material;
+// uniform sampler2D image_texture;
+
+out vec4 color;
+
+void main() {
+  color = vec4(vec3(texture(material.diffuse1, uv_coords)), 1.0);
+}
diff --git a/shaders/pp_vert.glsl b/shaders/pp_vert.glsl
new file mode 100644
index 0000000..5643ec6
--- /dev/null
+++ b/shaders/pp_vert.glsl
@@ -0,0 +1,12 @@
+#version 330 core
+
+layout(location=0) in vec3 position;
+layout(location=1) in vec3 normal;
+layout(location=2) in vec2 uv;
+
+out vec2 uv_coords;
+
+void main() {
+  gl_Position = vec4(position.x, position.y, 0.0, 1.0);
+  uv_coords   = uv;
+}
diff --git a/src/main.cc b/src/main.cc
index cadf204..47b9550 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -54,6 +54,7 @@ enum exit_codes : int {
   EXIT_CODE_WINDOW_CREATION_FAILED,
   EXIT_CODE_OPENGL_CONTEXT_FAILED,
   EXIT_CODE_GLAD_LOADER_FAILED,
+  EXIT_CODE_INCOMPLETE_FRAME_BUFFER,
 };
 
 class Shader {
@@ -135,6 +136,15 @@ class Model {
     std::vector<Texture2D> load_material_textures(aiMaterial *mat, aiTextureType type);
 };
 
+struct FrameBuffer {
+  GLuint fbo;
+  GLuint color;
+  GLuint depth_stencil;
+};
+
+FrameBuffer create_frame_buffer(unsigned int width, unsigned int height);
+void delete_frame_buffer(FrameBuffer &buffer);
+
 int main() {
   if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
     return EXIT_CODE_SDL_INIT_FAILED;
@@ -166,8 +176,7 @@ int main() {
 
   glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
 
-  glEnable(GL_DEPTH_TEST);
-  glEnable(GL_CULL_FACE);
+  FrameBuffer offscreen_buffer = create_frame_buffer(WINDOW_WIDTH, WINDOW_HEIGHT);
 
   std::vector<Vertex> vertices = {
     // positions                           // normals                      // texture coords
@@ -215,8 +224,23 @@ int main() {
   Model backpack = {"models/suzanne/suzanne.obj"};
   Mesh light     = {vertices, indices, {}};
 
-  Shader main_shader  {"shaders/vert.glsl", "shaders/frag.glsl"};
-  Shader light_shader {"shaders/vert.glsl", "shaders/light_frag.glsl"};
+  std::vector<Vertex> screen_vertices = {
+    Vertex{glm::vec3(-1.0f, -1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(0.0f, 0.0f)},
+    Vertex{glm::vec3( 1.0f, -1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 0.0f)},
+    Vertex{glm::vec3(-1.0f,  1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(0.0f, 1.0f)},
+    Vertex{glm::vec3( 1.0f,  1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)},
+  };
+
+  std::vector<GLuint> screen_indices = {
+    0, 1, 2,
+    2, 1, 3,
+  };
+
+  Mesh screen = {screen_vertices, screen_indices, {}};
+
+  Shader main_shader     {"shaders/vert.glsl",    "shaders/frag.glsl"};
+  Shader light_shader    {"shaders/vert.glsl",    "shaders/light_frag.glsl"};
+  Shader post_processing {"shaders/pp_vert.glsl", "shaders/pp_frag.glsl"};
 
   const float camera_speed   = 25.0f;
   glm::vec3 camera_position  = glm::vec3(-2.0f, 0.0f, 6.0f);
@@ -356,6 +380,10 @@ int main() {
             SDL_GL_GetDrawableSize(wnd, &w, &h);
             glViewport(0, 0, w, h);
             SDL_WarpMouseInWindow(wnd, (int)(w * 0.5f), (int)(h * 0.5f));
+
+            // Recreate offscreen frame buffer
+            delete_frame_buffer(offscreen_buffer);
+            offscreen_buffer = create_frame_buffer(w, h);
           }
           break;
       }
@@ -375,8 +403,13 @@ int main() {
     main_shader.set_mat4("view", view);
     light_shader.set_mat4("view", view);
 
+    // Main render pass
+    glBindFramebuffer(GL_FRAMEBUFFER, offscreen_buffer.fbo);
+
     glClearColor(0.04f, 0.08f, 0.08f, 1.0f);
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    glEnable(GL_DEPTH_TEST);
+    glEnable(GL_CULL_FACE);
 
     model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f));
     normal_mat = glm::transpose(glm::inverse(model));
@@ -395,6 +428,23 @@ int main() {
       light.draw(light_shader);
     }
 
+    // wireframe mode
+    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+
+    // Post processing pass
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glDisable(GL_DEPTH_TEST);
+    glDisable(GL_CULL_FACE);
+
+    post_processing.activate();
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, offscreen_buffer.color);
+    post_processing.set_int("image_texture", 0);
+    screen.draw(post_processing);
+
     SDL_GL_SwapWindow(window);
   }
 
@@ -726,3 +776,43 @@ std::vector<Texture2D> Model::load_material_textures(aiMaterial *material, aiTex
 
   return textures;
 }
+
+FrameBuffer create_frame_buffer(unsigned int width, unsigned int height) {
+  FrameBuffer buffer = {};
+
+  glGenFramebuffers(1, &buffer.fbo);
+  glBindFramebuffer(GL_FRAMEBUFFER, buffer.fbo);
+
+  // Create color texture
+  glGenTextures(1, &buffer.color);
+  glBindTexture(GL_TEXTURE_2D, buffer.color);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, buffer.color, 0);
+
+  // Create depth and stencil buffers
+  glGenRenderbuffers(1, &buffer.depth_stencil);
+  glBindRenderbuffer(GL_RENDERBUFFER, buffer.depth_stencil);
+  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
+
+  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer.depth_stencil);
+
+  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+    printf("Incomplete frame buffer\n");
+    exit(EXIT_CODE_INCOMPLETE_FRAME_BUFFER);
+  }
+
+  glBindFramebuffer(GL_FRAMEBUFFER, 0);
+  glBindTexture(GL_TEXTURE_2D, 0);
+  glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+  return buffer;
+}
+
+void delete_frame_buffer(FrameBuffer &buffer) {
+  glDeleteFramebuffers(1, &buffer.fbo);
+  glDeleteTextures(1, &buffer.color);
+  glDeleteRenderbuffers(1, &buffer.depth_stencil);
+}