How to make multiple viewports work with GLFW

Hello folks,

I’m currently working on OpenGL application with GLFW, and having difficulties to make multiple viewports work.
Below is my code, I’m getting first window work only although I’m trying to draw same scene across all viewports.
Eventually I will separate work for each views (Top/Bottom, Perspective, Front/Back, Left/Right), but first thing is to make the all viewports works.
Can anyone give me some advice how to fix my problem?

Thank you so much!

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>

#include "GLFW/glfw3native.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <filesystem.h>
#include <shader.h>
#include <camera.h>
#include <model.h>

#include "windows.h"
#include <iostream>

using namespace std;
using namespace glm;

#define                 MAX_LOADSTRING 100
#define                 GLFW_EXPOSE_NATIVE_WGL
#define                 GLFW_EXPOSE_NATIVE_WIN32
#define					VIEWPORT_START											-1
#define					VIEWPORT_TOPLEFT										0
#define					VIEWPORT_TOPRIGHT										1
#define					VIEWPORT_BOTTOMLEFT										2
#define					VIEWPORT_BOTTOMRIGHT									3
#define					VIEWPORT_END											4

#define					SYSTEM_EXIT_NORMAL										0
#define					SYSTEM_EXIT_MAIN_WINDOW_CREATION_FAIL					-1
#define					SYSTEM_EXIT_GLVIEW_CREATION_FAIL						-2

// RGBA
struct RGBA {
    GLdouble r, g, b, a;

    RGBA(GLdouble _r = 0.0, GLdouble _g = 0.0, GLdouble _b = 0.0, GLdouble _a = 1.0) : r(_r), g(_g), b(_b), a(_a) {}
};

// Application
typedef struct {																// Contains information vital to a application
    char className[1024];														// Window class name
    HINSTANCE hInstance;														// Application instance
} Application;

// Window information
struct GL_Window {																// Contains information vital to a OpenGL Window
    HWND				hWnd;													// Window handle
    GLFWwindow*         glfwHandle;												// GLFW window handle

    char				className[256];											// Window class name

    int					x, y;													// x, y Position
    int					Width, Height;											// Width, Height

    int					viewport;												// Viewport

    RGBA				bgColor;												// Background color

    mat4				view;													// Viewport size information
    mat4				mvMatrix;												// Model view matrix
    mat4				prjMatrix;												// Projection view matrix

    Shader				shader, shaderLight, shaderBlur, shaderBloomFinal;      // Shaders
};


// Settings
int DisplayWidth, DisplayHeight;
int WindowWidth, WindowHeight;
int ViewWidth, ViewHeight;

bool bloom = true;
bool bloomKeyPressed = false;
float exposure = 1.0f;

// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 5.0f));
float lastX = (float)WindowWidth / 2.0;
float lastY = (float)WindowHeight / 2.0;
bool firstMouse = true;

// Timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;

HINSTANCE			hInst;
WCHAR				szTitle[MAX_LOADSTRING];
WCHAR				szWindowClass[MAX_LOADSTRING];

unsigned int		woodTexture;
unsigned int		containerTexture;
unsigned int		hdrFBO;

vector<glm::vec3>	lightPositions;
vector<glm::vec3>	lightColors;
unsigned int		pingpongFBO[2];
unsigned int		pingpongColorbuffers[2];
unsigned int		colorBuffers[2];

int					CreateGLWindows(GL_Window& glWindow);
void				framebuffer_size_callback(GLFWwindow* window, int width, int height);
void				mouse_callback(GLFWwindow* window, double xpos, double ypos);
void				scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void				processInput(GLFWwindow* window);
unsigned int		loadTexture(const char* path, bool gammaCorrection);
void				renderQuad();
void				renderCube();
void				DrawScene(GL_Window glWindow);

bool				RegisterWindowClass(Application* application);
bool				InitInstance(HINSTANCE hInstance, int nWidth, int nHeight);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);

void				InitGLWindows();

HWND				mainHwnd;
HMENU				mainMenu;
Application			application;
MSG					msg;    // Window message

GL_Window			glWindows[4];
    
// Main window procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
   // Main window callback...

    return 0;
}

// Register a window
bool RegisterWindowClass(Application* application) {
    // Register main window...

    return true;
}

// Initialize window
bool InitInstance(HINSTANCE hInstance, int nWidth, int nHeight) {
    // Initialize main window...

    return true;
}

// Create GLFW windows
int CreateGLWindows(GL_Window& glWindow) {
    // GLFW: Initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
    glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
    glfwWindowHint(GLFW_DECORATED, GL_FALSE);

    // GLFW window creation
    glWindow.glfwHandle = glfwCreateWindow(glWindow.Width, glWindow.Height, glWindow.className, NULL, NULL);

    // Get window handle
    glWindow.hWnd = glfwGetWin32Window(glWindow.glfwHandle);

    // Failed to create window?
    if (glWindow.glfwHandle == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(glWindow.glfwHandle);

    // Register callbacks
    glfwSetFramebufferSizeCallback(glWindow.glfwHandle, framebuffer_size_callback);
    glfwSetCursorPosCallback(glWindow.glfwHandle, mouse_callback);
    glfwSetScrollCallback(glWindow.glfwHandle, scroll_callback);

    // Refine window style
    long style = GetWindowLong(glWindow.hWnd, GWL_STYLE);
    style &= ~(WS_POPUP | WS_CAPTION); // remove popup style
    style |= WS_CHILD | CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    SetWindowLong(glWindow.hWnd, GWL_STYLE, style);

    // Tell GLFW to capture our mouse
    glfwSetInputMode(glWindow.glfwHandle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
//    glfwSetInputMode(glWindow.glfwHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // GLAD: Load all OpenGL function pointers
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // Configure global opengl state
    glEnable(GL_DEPTH_TEST);

    // Build and compile shaders
    glWindow.shader = Shader("bloom.vs", "bloom.fs");
    glWindow.shaderLight = Shader("bloom.vs", "light_box.fs");
    glWindow.shaderBlur = Shader("blur.vs", "blur.fs");
    glWindow.shaderBloomFinal = Shader("bloom_final.vs", "bloom_final.fs");

    // Load textures
    woodTexture = loadTexture(FileSystem::getPath("resources/textures/wood.png").c_str(), true); // Loading the texture as an SRGB texture
    containerTexture = loadTexture(FileSystem::getPath("resources/textures/container2.png").c_str(), true); // Loading the texture as an SRGB texture

    // Configure (floating point) framebuffers
    glGenFramebuffers(1, &hdrFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);

    // Create 2 floating point color buffers (1 for normal rendering, other for brightness threshold values)
    glGenTextures(2, colorBuffers);

    for (unsigned int i = 0; i < 2; i++) {
        glBindTexture(GL_TEXTURE_2D, colorBuffers[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, glWindow.Width, glWindow.Height, 0, GL_RGBA, GL_FLOAT, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // We clamp to the edge as the blur filter would otherwise sample repeated texture values!
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        // Attach texture to framebuffer
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0);
    }

    // Create and attach depth buffer (renderbuffer)
    unsigned int rboDepth;
    glGenRenderbuffers(1, &rboDepth);
    glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, glWindow.Width, glWindow.Height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);

    // Tell OpenGL which color attachments we'll use (of this framebuffer) for rendering 
    unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
    glDrawBuffers(2, attachments);

    // Finally check if framebuffer is complete
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "Framebuffer not complete!" << std::endl;

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Ping-pong-framebuffer for blurring
    glGenFramebuffers(2, pingpongFBO);
    glGenTextures(2, pingpongColorbuffers);

    for (unsigned int i = 0; i < 2; i++) {
        glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
        glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, glWindow.Width, glWindow.Height, 0, GL_RGBA, GL_FLOAT, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // We clamp to the edge as the blur filter would otherwise sample repeated texture values!
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongColorbuffers[i], 0);

        // Also check if framebuffers are complete (no need for depth buffer)
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            std::cout << "Framebuffer not complete!" << std::endl;
    }

    // Lighting info, positions
    lightPositions.push_back(glm::vec3(0.0f, 0.5f, 1.5f));

    // Colors
    lightColors.push_back(glm::vec3(5.0f, 5.0f, 5.0f));

    // Shader configuration
    glWindow.shader.use();
    glWindow.shader.setInt("diffuseTexture", 0.1);
    glWindow.shaderBlur.use();
    glWindow.shaderBlur.setInt("image", 0);
    glWindow.shaderBloomFinal.use();
    glWindow.shaderBloomFinal.setInt("scene", 0);
    glWindow.shaderBloomFinal.setInt("bloomBlur", 1);

    // Attach to parnet window
    SetParent(glWindow.hWnd, mainHwnd);

    // Change the window position
    glfwSetWindowPos(glWindow.glfwHandle, glWindow.x, glWindow.y);

    // Show window
    glfwShowWindow(glWindow.glfwHandle);

    // Set clear color
    glClearColor(glWindow.bgColor.r, glWindow.bgColor.g, glWindow.bgColor.b, glWindow.bgColor.a);

    return 0;
}

void DrawScene(GL_Window glWindow) {
    if (!glfwWindowShouldClose(glWindow.glfwHandle)) {
        // Change the calling thread's current rendering context 
        glfwMakeContextCurrent(glWindow.glfwHandle);

        // Per-frame time logic
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // Input
        processInput(glWindow.glfwHandle);

        // Render
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 1. Render scene into floating point framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glWindow.prjMatrix = glm::perspective(glm::radians(camera.Zoom), (float)glWindow.Width / (float)glWindow.Height, 0.1f, 100.0f);
        glWindow.view = camera.GetViewMatrix();
        glWindow.mvMatrix = glm::mat4(1.0f);

        glWindow.shader.use();
        glWindow.shader.setMat4("projection", glWindow.prjMatrix);
        glWindow.shader.setMat4("view", glWindow.view);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, woodTexture);

        // Set lighting uniforms
        glWindow.shader.setVec3("lights[" + std::to_string(0) + "].Position", lightPositions[0]);
        glWindow.shader.setVec3("lights[" + std::to_string(0) + "].Color", lightColors[0]);

        glWindow.shader.setVec3("viewPos", camera.Position);

        // Create one large cube that acts as the floor
        glWindow.mvMatrix = glm::mat4(1.0f);
        glWindow.mvMatrix = glm::translate(glWindow.mvMatrix, glm::vec3(0.0f, -1.0f, 0.0));
        glWindow.mvMatrix = glm::scale(glWindow.mvMatrix, glm::vec3(12.5f, 0.5f, 12.5f));
        glWindow.shader.setMat4("model", glWindow.mvMatrix);
        renderCube();

        // Finally show all the light sources as bright cubes
        glWindow.shaderLight.use();
        glWindow.shaderLight.setMat4("projection", glWindow.prjMatrix);
        glWindow.shaderLight.setMat4("view", glWindow.view);

        glWindow.mvMatrix = glm::mat4(1.0f);
        glWindow.mvMatrix = glm::translate(glWindow.mvMatrix, glm::vec3(lightPositions[0]));
        glWindow.mvMatrix = glm::scale(glWindow.mvMatrix, glm::vec3(0.25f));
        glWindow.shaderLight.setMat4("model", glWindow.mvMatrix);
        glWindow.shaderLight.setVec3("lightColor", lightColors[0]);
        renderCube();

        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        // 2. Blur bright fragments with two-pass Gaussian Blur 
        bool horizontal = true, first_iteration = true;
        glWindow.shaderBlur.use();

        glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
        glWindow.shaderBlur.setInt("horizontal", horizontal);

        // Bind texture of other framebuffer (or scene if first iteration)
        glBindTexture(GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongColorbuffers[!horizontal]);
        renderQuad();
        horizontal = !horizontal;

        if (first_iteration)
            first_iteration = false;

        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        // 3. Now render floating point color buffer to 2D quad and tonemap HDR colors to default framebuffer's (clamped) color range
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glWindow.shaderBloomFinal.use();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, colorBuffers[0]);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[!horizontal]);
        glWindow.shaderBloomFinal.setInt("bloom", bloom);
        glWindow.shaderBloomFinal.setFloat("exposure", exposure);
        renderQuad();

        // Swap buffers
        glfwSwapBuffers(glWindow.glfwHandle);
    }
}

// Renders a 1x1 3D cube in NDC.
unsigned int cubeVAO = 0;
unsigned int cubeVBO = 0;
void renderCube() {
    // Render cube...
}

// Renders a 1x1 XY quad in NDC
unsigned int quadVAO = 0;
unsigned int quadVBO;
void renderQuad() {
    // Render quad...
}

// Process all input: Query GLFW whether relevant keys are pressed/released this frame and react accordingly
void processInput(GLFWwindow* window) {
    // Keyboard inputs handling...
}

// GLFW: Whenever the window size changed (by OS or user resize) this callback function executes
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

// GLFW: Whenever the mouse moves, this callback is called
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
    // Mouse movement events handling...
}

// GLFW: Whenever the mouse scroll wheel scrolls, this callback is called
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
    // Mouse scroll events handling...
}

// Utility function for loading a 2D texture from file
unsigned int loadTexture(char const* path, bool gammaCorrection) {
    unsigned int textureID;
    
    // Load textures...

    return textureID;
}

// Initialize GL Windows
void InitGLWindows() {
    // Viewport initialization...
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) {
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_POINT3D, szWindowClass, MAX_LOADSTRING);

    // Load accelerators
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_POINT3D));

    // Fill Out Application Data
    sprintf(application.className, "%s", "Point3D");
    application.hInstance = hInstance;

    // Window width & height
    WindowWidth = 1920;
    WindowHeight = 1080;

    // Get current display size
    DisplayWidth = GetSystemMetrics(SM_CXFULLSCREEN);
    DisplayHeight = GetSystemMetrics(SM_CYFULLSCREEN);

    // Register class for main window
    if (RegisterWindowClass(&application) == false)
        return SYSTEM_EXIT_MAIN_WINDOW_CREATION_FAIL;

    // Initialize window
    if (InitInstance(hInstance, WindowWidth, WindowHeight) == true) {
        // Initialize GL Windows
        InitGLWindows();

        // Create GL Windows
        for (int i = VIEWPORT_TOPLEFT; i < VIEWPORT_END; i++) {
            if (CreateGLWindows(glWindows[i]) != 0)
                return SYSTEM_EXIT_GLVIEW_CREATION_FAIL;
        }
    }

    // Message pump
    while (GetMessage(&msg, nullptr, 0, 0)) {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);

            // Main loop
            for (;;) {
                for (int x = VIEWPORT_TOPLEFT; x < VIEWPORT_END; x++) {
                     DrawScene(glWindows[x]);
                }

                glfwWaitEvents();
            }
        }
    }

    return (int)msg.wParam;
}