Search code examples
c++openglrenderingglfwframebuffer

How to use a renderbuffer and glBlit to render to four Windows, with GLFW


Problem Statement

This should be a really obvious answer and somewhere I've probably mucked up a line or two, and yet I can't seem to get triangles to draw to a framebuffer.

What I'm trying to do is get two triangles to a large render buffer object, with a framebuffer attached, and then in one of four windows display a slice of the larger render buffer/framebuffer using glBlitFramebuffer.

I'm using init_FB() to define the triangles to render and the RBO in which to render those triangles. I've created a renderbuffer object and a framebuffer object associated with the renderbuffer object. In the compute_FB() function I am binding the framebuffer of the RBO and then making a call to draw into that framebuffer. Before drawing the triangles, I am clearing the framebuffer to a specific color, royalblue.


What's Really Happening

In the first window, called window, what's appearing is only the color royalblue defined by the function (compute_FB()) that draws into the framebuffer of the renderbuffer object. However, none of the triangles are being drawn eventhough I have a glDrawArrays(...) function being called at the end of compute_FB().

Possible Hypothesis for What's Happening

I'm beginning to believe that the RBO needs its own context in which to render successfully, but I don't know how to set up a context for a RBO. I thought contexts were only for windows in GLFW.


Explanation of Code

I am basing my attempt on the initial OpenGL Redbook example 01-triangles. In this example I've coded four unique windows and want eventually to copy a large RBO/framebuffer to each of the four windows - currently I'm just focusing on the first display.

I am using OpenGL4.5 with GLFW for windowing.


CODE

//////////////////////////////////////////////////////////////////////////////
//
//  Triangles.cpp
//
//////////////////////////////////////////////////////////////////////////////

#include "vgl.h"
#include "LoadShaders.h"
#include <vector>

enum VAO_IDs { Triangles, NumVAOs };
enum Buffer_IDs { ArrayBuffer, NumBuffers };
enum Attrib_IDs { vPosition = 0 };

GLuint  VAOs[NumVAOs];
GLuint  Buffers[NumBuffers];

const GLuint  NumVertices = 6;

//////////////////////////////////////////////////
// Framebuffer Variables
//////////////////////////////////////////////////
enum {Color, NumRenderBuffers};
GLuint framebuffer, renderbuffer[NumRenderBuffers];
GLuint fbwidth = 3200;
GLuint fbheight = 600;


//----------------------------------------------------------------------------
//
// init
//
void init_FB( void )
{
    // Create an Empty RenderBuffer and Associated Framebuffer
    glCreateRenderbuffers(NumRenderBuffers, renderbuffer);
    glNamedRenderbufferStorage(renderbuffer[Color], GL_RGBA, fbwidth, fbheight);
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
    glNamedFramebufferRenderbuffer(framebuffer, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer[Color]);
    glEnable(GL_DEPTH_TEST);


    // Here's some info to initialize for the RBO
    // The framebuffer for the RBO has been bound (above) and ?SHOULD? be ready to draw to, right?
    glGenVertexArrays( NumVAOs, VAOs );
    glBindVertexArray( VAOs[Triangles] );

    GLfloat  vertices[NumVertices][2] = {
        { -1.00f, -1.00f }, { -1.00f,  0.40f }, {  0.00f, -1.00f },  // Triangle 1
        {  0.00f,  0.40f }, {  0.40f,  0.40f }, {  0.40f, -0.40f }   // Triangle 2
    };

    ShaderInfo  shaders[] =
        {
            { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" },
            { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" },
            { GL_NONE, NULL }
        };

    GLuint program = LoadShaders( shaders );
    glUseProgram( program );

    glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                           GL_FALSE, 0, BUFFER_OFFSET(0) );
    glEnableVertexAttribArray( vPosition );
}


void init( void )
{
    // Create the standard window framebuffer for this window context
    // Basically, I want to give the window a framebuffer so that I can draw into it later in the 'draw' phase
    glCreateBuffers( NumBuffers, Buffers );
    glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] );

    static const float black[] = { 1.0f, 0.5f, 0.2f, 0.0f };

    // May as well clear it to a color that's visually separate from the color that it will be cleared to
    //      .. in the draw phase.
    glClearBufferfv(GL_COLOR, 0, black);
}


void init2( void )
{
    glGenVertexArrays( NumVAOs, VAOs );
    glBindVertexArray( VAOs[Triangles] );

    GLfloat  vertices[NumVertices][2] = {
        { -0.90f, -0.60f }, { -0.85f, -0.60f }, { -0.50f, -0.65f },  // Triangle 1
        {  0.90f, -0.85f }, {  0.90f,  0.90f }, { -0.85f,  0.90f }   // Triangle 2
    };

    glCreateBuffers( NumBuffers, Buffers );
    glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] );
    glBufferStorage( GL_ARRAY_BUFFER, sizeof(vertices), vertices, 0);

    ShaderInfo  shaders[] =
    {
        { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" },
        { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" },
        { GL_NONE, NULL }
    };

    GLuint program = LoadShaders( shaders );
    glUseProgram( program );

    glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                           GL_FALSE, 0, BUFFER_OFFSET(0) );
    glEnableVertexAttribArray( vPosition );
}


void init3( void )
{
    glGenVertexArrays( NumVAOs, VAOs );
    glBindVertexArray( VAOs[Triangles] );

    GLfloat  vertices[NumVertices][2] = {
        { -0.90f, -0.90f }, { -0.90f,  0.90f }, {  0.00f, -0.90f },  // Triangle 1
        {  0.00f,  0.90f }, {  0.90f,  0.90f }, {  0.90f, -0.90f }   // Triangle 2
    };

    glCreateBuffers( NumBuffers, Buffers );
    glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] );
    glBufferStorage( GL_ARRAY_BUFFER, sizeof(vertices), vertices, 0);

    ShaderInfo  shaders[] =
    {
        { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" },
        { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" },
        { GL_NONE, NULL }
    };

    GLuint program = LoadShaders( shaders );
    glUseProgram( program );

    glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                           GL_FALSE, 0, BUFFER_OFFSET(0) );
    glEnableVertexAttribArray( vPosition );
}


void init4( void )
{
    glGenVertexArrays( NumVAOs, VAOs );
    glBindVertexArray( VAOs[Triangles] );

    GLfloat  vertices[NumVertices][2] = {
        { -0.40f, -0.40f }, { -0.40f,  0.40f }, {  0.00f, -0.40f },  // Triangle 1
        {  0.00f,  0.40f }, {  0.40f,  0.40f }, {  0.40f, -0.40f }   // Triangle 2
    };

    glCreateBuffers( NumBuffers, Buffers );
    glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] );
    glBufferStorage( GL_ARRAY_BUFFER, sizeof(vertices), vertices, 0);

    ShaderInfo  shaders[] =
    {
        { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" },
        { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" },
        { GL_NONE, NULL }
    };

    GLuint program = LoadShaders( shaders );
    glUseProgram( program );

    glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                           GL_FALSE, 0, BUFFER_OFFSET(0) );
    glEnableVertexAttribArray( vPosition );
}


//----------------------------------------------------------------------------
//
// display
//

void compute_FB()
{
    // Prepare to render into the framebuffer
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
    glViewport(0, 0, fbwidth, fbheight);

    // Clear before drawing. This shade of color comes through to the first window display
    static const float black[] = { 0.0f, 0.3f, 0.8f, 0.0f };

    glClearBufferfv(GL_COLOR, 0, black);

    // Try drawing the triangles... Nuthin
    glBindVertexArray( VAOs[Triangles] );
    glDrawArrays( GL_TRIANGLES, 0, NumVertices );
}

// Read from a section of the RBO/framebuffer
void display( void )
{
    static const float black[] = { 0.8f, 0.0f, 0.0f, 0.0f };

    glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); // Set framebuffer to read from
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // set window to draw to
    glViewport(0, 0, 800, 600); // Probbaly not needed

    // Copy from READ framebuffer to DRAW framebuffer
    // QUESTION: Why isn't this copying to just a small corner of the window context's framebuffer?
    glBlitFramebuffer(0, 0, fbwidth, fbheight, 0, 0, 100, 200, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}

void display2( void )
{
    static const float black[] = { 0.0f, 0.3f, 0.4f, 0.0f };

    glClearBufferfv(GL_COLOR, 0, black);

    glBindVertexArray( VAOs[Triangles] );
    glDrawArrays( GL_TRIANGLES, 0, NumVertices );
}

void display3( void )
{
    static const float black[] = { 0.7f, 0.6f, 0.4f, 0.0f };

    glClearBufferfv(GL_COLOR, 0, black);

    glBindVertexArray( VAOs[Triangles] );
    glDrawArrays( GL_TRIANGLES, 0, NumVertices );
}

void display4( void )
{
    static const float black[] = { 0.2f, 0.3f, 0.7f, 0.0f };

    glClearBufferfv(GL_COLOR, 0, black);

    glBindVertexArray( VAOs[Triangles] );
    glDrawArrays( GL_TRIANGLES, 0, NumVertices );
}


//----------------------------------------------------------------------------
//
// main
//

#ifdef _WIN32
int CALLBACK WinMain(
  _In_ HINSTANCE hInstance,
  _In_ HINSTANCE hPrevInstance,
  _In_ LPSTR     lpCmdLine,
  _In_ int       nCmdShow
)
#else
int
main( int argc, char** argv )
#endif
{
    // Initialize GLFW
    glfwInit();


    //TODO Create Windows Class
    // Create Windows
    GLFWwindow* window = glfwCreateWindow(800, 600, "Triangles", NULL, NULL);
    GLFWwindow* window2 = glfwCreateWindow(800, 600, "Triangles2", NULL, NULL);
    GLFWwindow* window3 = glfwCreateWindow(800, 600, "Triangles3", NULL, NULL);
    GLFWwindow* window4 = glfwCreateWindow(800, 600, "Triangles4", NULL, NULL);



    // Initialize OpenGL
    gl3wInit();


    // Framebuffer Initialization
    init_FB();


    // Initialize Windows
    glfwMakeContextCurrent(window);
    init();
    glfwMakeContextCurrent(window2);
    init2();
    glfwMakeContextCurrent(window3);
    init3();
    glfwMakeContextCurrent(window4);
    init4();


    // Draw the Windows
    while (!glfwWindowShouldClose(window) && !glfwWindowShouldClose(window2) && !glfwWindowShouldClose(window3) && !glfwWindowShouldClose(window4))
    {
        glfwMakeContextCurrent(window);
        compute_FB();
        display();
        glfwSwapBuffers(window);
        glfwPollEvents();

        glfwMakeContextCurrent(window2);
        display2();
        glfwSwapBuffers(window2);
        glfwPollEvents();

        glfwMakeContextCurrent(window3);
        display3();
        glfwSwapBuffers(window3);
        glfwPollEvents();

        glfwMakeContextCurrent(window4);
        display4();
        glfwSwapBuffers(window4);
        glfwPollEvents();
    }


    // Destroy Windows
    glfwDestroyWindow(window);
    glfwDestroyWindow(window2);
    glfwDestroyWindow(window3);
    glfwDestroyWindow(window4);


    // Terminate GLFW Instance
    glfwTerminate();
}


Edit #2

Thanks to @Ripi2, I am now able to use glBlit and a renderbuffer. Somehow though I am not correctly using either or both of the renderbuffer and different FBO for the second window. NOTE At this point, I am not yet implementing the glBlit on the third or fourth windows (though I will, once I can succesfully integrate the renderbuffer and glBlit into the second window)

Edit #2 Code

//////////////////////////////////////////////////////////////////////////////
//
//  Triangles.cpp
//
//////////////////////////////////////////////////////////////////////////////
#include <cstdio>
#include "vgl.h"
#include "LoadShaders.h"

enum VAO_IDs { Triangles, NumVAOs };
enum Buffer_IDs { ArrayBuffer, NumBuffers };
enum Attrib_IDs { vPosition = 0 };

GLuint  VAOs[NumVAOs];
GLuint  Buffers[NumBuffers];

const GLuint  NumVertices = 6;
////////////////////////////////////
//RBO variables
enum {Color=0, NumRenderBuffers=1, NumFBOs=4};
GLuint renderbuffer[NumRenderBuffers], fbos[NumFBOs];
GLuint buffwidth = 3200;
GLuint buffheight = 600;


//----------------------------------------------------------------------------
//
// init
//

void
init( void )
{
    glCreateRenderbuffers(NumRenderBuffers, renderbuffer);
    glNamedRenderbufferStorage(renderbuffer[Color], GL_RGBA, buffwidth, buffheight);
    glGenFramebuffers(1, &fbos[0]);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[0]);
    glNamedFramebufferRenderbuffer(fbos[0], GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer[Color]);

    glGenVertexArrays( NumVAOs, VAOs );
    glBindVertexArray( VAOs[Triangles] );

    GLfloat  vertices[NumVertices][2] = {
        { -0.90f, -0.90f }, { -0.90f,  0.90f }, {  0.00f, -0.90f },  // Triangle 1
        {  0.00f,  0.90f }, {  0.90f,  0.90f }, {  0.90f, -0.90f }   // Triangle 2
    };

    glCreateBuffers( NumBuffers, Buffers );
    glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] );
    glBufferStorage( GL_ARRAY_BUFFER, sizeof(vertices), vertices, 0);

    ShaderInfo  shaders[] =
    {
        { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" },
        { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" },
        { GL_NONE, NULL }
    };

    GLuint program = LoadShaders( shaders );
    glUseProgram( program );

    glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                           GL_FALSE, 0, BUFFER_OFFSET(0) );
    glEnableVertexAttribArray( vPosition );
}


void
init2( void )
{
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[Color]);
    glGenFramebuffers(1, &fbos[1]);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[1]);
    glNamedFramebufferRenderbuffer(fbos[1], GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, renderbuffer[Color]);

}


void
init3( void )
{
    glGenVertexArrays( NumVAOs, VAOs );
    glBindVertexArray( VAOs[Triangles] );

    GLfloat  vertices[NumVertices][2] = {
        { -0.90f, -0.90f }, { -0.90f,  0.90f }, {  0.00f, -0.90f },  // Triangle 1
        {  0.00f,  0.90f }, {  0.90f,  0.90f }, {  0.90f, -0.90f }   // Triangle 2
    };

    glCreateBuffers( NumBuffers, Buffers );
    glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] );
    glBufferStorage( GL_ARRAY_BUFFER, sizeof(vertices), vertices, 0);

    ShaderInfo  shaders[] =
    {
        { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" },
        { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" },
        { GL_NONE, NULL }
    };

    GLuint program = LoadShaders( shaders );
    glUseProgram( program );

    glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                           GL_FALSE, 0, BUFFER_OFFSET(0) );
    glEnableVertexAttribArray( vPosition );
}


void
init4( void )
{
    glGenVertexArrays( NumVAOs, VAOs );
    glBindVertexArray( VAOs[Triangles] );

    GLfloat  vertices[NumVertices][2] = {
        { -0.40f, -0.40f }, { -0.40f,  0.40f }, {  0.00f, -0.40f },  // Triangle 1
        {  0.00f,  0.40f }, {  0.40f,  0.40f }, {  0.40f, -0.40f }   // Triangle 2
    };

    glCreateBuffers( NumBuffers, Buffers );
    glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] );
    glBufferStorage( GL_ARRAY_BUFFER, sizeof(vertices), vertices, 0);

    ShaderInfo  shaders[] =
    {
        { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" },
        { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" },
        { GL_NONE, NULL }
    };

    GLuint program = LoadShaders( shaders );
    glUseProgram( program );

    glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                           GL_FALSE, 0, BUFFER_OFFSET(0) );
    glEnableVertexAttribArray( vPosition );
}


//----------------------------------------------------------------------------
//
// display
//

void
display( void )
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[0]);
    glViewport(0, 0, buffwidth, buffheight);
    static const float black[] = { 0.2f, 0.2f, 0.2f, 0.0f };
    static const float redish[] = { 0.6f, 0.4f, 0.3f, 0.0f };
    glClearBufferfv(GL_COLOR, 0, black);

    glBindVertexArray( VAOs[Triangles] );
    glDrawArrays( GL_TRIANGLES, 0, NumVertices );


    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glViewport(0, 0, 800, 600);
    glClearBufferfv(GL_COLOR, 0, redish);

    glBlitFramebuffer(0, 0, 800, 600, 0, 0, 800, 600, GL_COLOR_BUFFER_BIT, GL_NEAREST);

}

void
display2( void )
{
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glViewport(0, 0, 800, 600);
    static const float redish[] = { 0.6f, 0.4f, 0.3f, 0.0f };
    glClearBufferfv(GL_COLOR, 0, redish);

    glBlitFramebuffer(buffwidth, 0, buffwidth+800, 600, 0, 0, 800, 600, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}

void
display3( void )
{
    static const float black[] = { 0.7f, 0.6f, 0.4f, 0.0f };

    glClearBufferfv(GL_COLOR, 0, black);

    glBindVertexArray( VAOs[Triangles] );
    glDrawArrays( GL_TRIANGLES, 0, NumVertices );
}

void
display4( void )
{
    static const float black[] = { 0.2f, 0.3f, 0.7f, 0.0f };

    glClearBufferfv(GL_COLOR, 0, black);

    glBindVertexArray( VAOs[Triangles] );
    glDrawArrays( GL_TRIANGLES, 0, NumVertices );
}


//----------------------------------------------------------------------------
//
// main
//

#ifdef _WIN32
int CALLBACK WinMain(
  _In_ HINSTANCE hInstance,
  _In_ HINSTANCE hPrevInstance,
  _In_ LPSTR     lpCmdLine,
  _In_ int       nCmdShow
)
#else
int
main( int argc, char** argv )
#endif
{
    // Initialize GLFW
    glfwInit();
    // Initialize OpenGL
    // Place it here before any OpenGL objects are needed, other OpenGL crashes
    //      ... in a "Segmentation fault (core dumped)" error
    gl3wInit();


    //TODO Create Windows Class
    // Create Windows
    GLFWwindow* window = glfwCreateWindow(800, 600, "Triangles", NULL, NULL);
    GLFWwindow* window2 = glfwCreateWindow(800, 600, "Triangles2", NULL, window);
    GLFWwindow* window3 = glfwCreateWindow(800, 600, "Triangles3", NULL, window);
    GLFWwindow* window4 = glfwCreateWindow(800, 600, "Triangles4", NULL, window);

    // Initialize Windows
    glfwMakeContextCurrent(window);
    init();
    glfwMakeContextCurrent(window2);
    init2();
    glfwMakeContextCurrent(window3);
    init3();
    glfwMakeContextCurrent(window4);
    init4();


    // Draw the Windows
    while (!glfwWindowShouldClose(window) && !glfwWindowShouldClose(window2) && !glfwWindowShouldClose(window3) && !glfwWindowShouldClose(window4))
    {
        glfwMakeContextCurrent(window);
        display();
        glfwSwapBuffers(window);
        glfwPollEvents();

        glfwMakeContextCurrent(window2);
        display2();
        glfwSwapBuffers(window2);
        glfwPollEvents();

        glfwMakeContextCurrent(window3);
        display3();
        glfwSwapBuffers(window3);
        glfwPollEvents();

        glfwMakeContextCurrent(window4);
        display4();
        glfwSwapBuffers(window4);
        glfwPollEvents();
    }


    // Destroy Windows
    glfwDestroyWindow(window);
    glfwDestroyWindow(window2);
    glfwDestroyWindow(window3);
    glfwDestroyWindow(window4);


    // Terminate GLFW Instance
    glfwTerminate();
}

Solution

  • Finally found the solution.

    Notes

    • I want to thank Ripi2 for nudges in the right direction with documentation and initial review of code. I really appreciate the help and guidance.
    • The code posted in this answer is not sophisticated, nor is it intended to be, therefore this is obviously not production quality code. What I'm outlining here should be easy to read by anyone - be they beginner or advanced
    • As an less experienced OpenGL programmer, there are probably stylistic choices being made that are not optimal or best practices. I will update this answer to reflect any workable improvement to this code that still maintains an easy readability for newcomers to the concepts.
    • A more complex windowing system that I'd like to adopt would look something like this. The developer here has created a very robust windowing system. If I were to make a change, it would be to create a window class. But... that's an academic discussion and highly tangential to the answer at hand.

    What was the Solution?

    1. Approaching context sharing. Again, thanks to Ripi2 for this. Originally, I was going to render a large framebuffer in a separate context, as in a fifth window, invisible window, and then perform glBlit copies to each active window. This may have worked, but upon guidance I was talked down to performing the actual rendering in the main window and then copying into the final three windows. This is faster because there are less operations
    2. Actually turning on Context Sharing with GLFW. At the end of each glfwCreateWindow(); command is a parameter in which to pass the context from another window. Since I was creating the renderbuffer in the initial init() for the first window, I had to set window for the final param for all three other windows, like so: GLFWwindow *window2 = glfwCreateWindow(width, height, title, NULL, window-whose-context-i-want-to-share);
    3. The difficult part, for me, was knowing exactly how to navigate the idea that a renderbuffer object can be shared, and so can its data, but you can only get access to it through an item that can't be shared across contexts. This leads to the idea of having one renderbuffer for all windows, and one framebuffer for each window. It turns out that actually implementing this was more simple than expected - but that was after hours of reading and research. Hopefully this answer will help you skip right to the heart of the matter.
    4. Each call to one of the init functions created a framebuffer and binds the renderbuffer to the current context.
    5. You have to rebind the renderbuffer to the current context, eventhough it's a shared resource. Not doing this will not load the rendered data from the first display() function. In other words, while the rendered data will still exist, you won't have access to it until you bind it in your current context... everytime you change to rendering into a new window.
    6. The REALLY important part, that I just had to figure out, was ensuring that I was setting the proper renderbuffer-target. In this case, what you'll notice in init2(const int) is the command that binds the current context's framebuffer to the shared renderbuffer, glNamedFramebufferRenderbuffer(...), must have the same renderbuffer-target as the original renderbuffer-target defined in init(): GL_COLOR_ATTACHMENT0. In hindsight, this should have been obvious, but the amount of documentation for multi-window rendering from a renderbuffer object was hard to find. Almost all others gave demonstrations using glTexture objects instead of renderbuffers. This GL_COLOR_ATTACHMENTi is like an internal address where multiple sets of rendered data can be stored... a very good hint for future projects.
    7. When binding the framebuffer for each context in the display2(const int) function, the important thing to set here was that the framebuffer was only going to READ from the renderbuffer, so use GL_READ_FRAMEBUFFER - because the data is already there and we're just using a framebuffer object as a way to get access to the data inside the renderbuffer. The FBO is the conduit, instead of the storage. Indeed, there is no way to access the data inside a renderbuffer without attaching an FBO first.
    8. Debugging - I started using various versions of the glGet function family in order to get information about the renderbuffer in different contexts, as well as the framebuffer. When the results from the gets matched my expectations, I knew I was on the right track. You'll see in display2(const int) that I left two lines in that get a renderbuffer parameter: the width. I left it in this code snippet for anyone else who might be interested in how this would work. WARNING printing from a display function is a bad idea for anything other than rudimentary debugging, because it will write the console once per cycle - and writing to stdout is ridiculously slow.

    The Code

    //////////////////////////////////////////////////////////////////////////////
    //
    //  Triangles.cpp
    //
    //////////////////////////////////////////////////////////////////////////////
    #include <cstdio>
    #include "vgl.h"
    #include "LoadShaders.h"
    
    enum VAO_IDs { Triangles, NumVAOs };
    enum Buffer_IDs { ArrayBuffer, NumBuffers };
    enum Attrib_IDs { vPosition = 0 };
    
    GLuint  VAOs[NumVAOs];
    GLuint  Buffers[NumBuffers];
    
    const GLuint  NumVertices = 6;
    ////////////////////////////////////
    //RBO variables
    enum {Color=0, NumRenderBuffers=1, NumFBOs=4};
    GLuint renderbuffer[NumRenderBuffers], fbos[NumFBOs];
    GLuint buffwidth = 3200;
    GLuint buffheight = 600;
    
    
    
    //----------------------------------------------------------------------------
    //
    // init
    //
    
    void
    init( void )
    {
        glCreateRenderbuffers(NumRenderBuffers, renderbuffer);
        glNamedRenderbufferStorage(renderbuffer[Color], GL_RGBA, buffwidth, buffheight);
        glGenFramebuffers(1, &fbos[0]);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[0]);
        glNamedFramebufferRenderbuffer(fbos[0], GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer[Color]);
        printf("\"init()\", is RB valid: %i\n", glIsRenderbuffer(renderbuffer[Color]));
        printf("\"init()\", is FBO valid: %i\n", glCheckNamedFramebufferStatus(fbos[0], GL_DRAW_FRAMEBUFFER));
    
        glGenVertexArrays( NumVAOs, VAOs );
        glBindVertexArray( VAOs[Triangles] );
    
        GLfloat  vertices[NumVertices][2] = {
            { -0.90f, -0.90f }, { -0.90f,  0.90f }, {  0.00f, -0.90f },  // Triangle 1
            {  0.00f,  0.90f }, {  0.90f,  0.90f }, {  0.90f, -0.90f }   // Triangle 2
        };
    
        glCreateBuffers( NumBuffers, Buffers );
        glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] );
        glBufferStorage( GL_ARRAY_BUFFER, sizeof(vertices), vertices, 0);
    
        ShaderInfo  shaders[] =
        {
            { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" },
            { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" },
            { GL_NONE, NULL }
        };
    
        GLuint program = LoadShaders( shaders );
        glUseProgram( program );
    
        glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                               GL_FALSE, 0, BUFFER_OFFSET(0) );
        glEnableVertexAttribArray( vPosition );
    }
    
    void
    init2( const int idx_in )
    {
        glGenFramebuffers(1, &fbos[idx_in]);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[idx_in]);
        glEnable(GL_RENDERBUFFER);
        glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[Color]);
        GLint tmpwidth = 0;
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &tmpwidth);
    
        glNamedFramebufferRenderbuffer(fbos[idx_in], GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer[Color]);
        printf("\"init2()\", RB width is: %i\n", tmpwidth);
        printf("\"init2()\", is RB valid: %i\n", glIsRenderbuffer(renderbuffer[Color]));
        printf("\"init2()\", is FBO valid: %i\n", glCheckNamedFramebufferStatus(fbos[idx_in], GL_DRAW_FRAMEBUFFER));
    
    }
    
    //----------------------------------------------------------------------------
    //
    // display
    //
    
    void
    display( void )
    {
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[0]);
        glViewport(0, 0, buffwidth, buffheight);
        static const float black[] = { 0.2f, 0.2f, 0.2f, 0.0f };
        static const float redish[] = { 0.6f, 0.4f, 0.3f, 0.0f };
        glClearBufferfv(GL_COLOR, 0, black);
    
        glBindVertexArray( VAOs[Triangles] );
        glDrawArrays( GL_TRIANGLES, 0, NumVertices );
    
    
        glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glViewport(0, 0, 800, 600);
        glClearBufferfv(GL_COLOR, 0, redish);
    
        glBlitFramebuffer(0, 0, 800, 600, 0, 0, 800, 600, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    
    }
    
    void
    display2( const int idx_in )
    {
        glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[idx_in]);
    
        GLint tmpwidth = 0;
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &tmpwidth);
    
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glViewport(0, 0, 800, 600);
    
    
        glBlitFramebuffer(800 * idx_in - 1, 0, 800 * idx_in - 1 + 800, 600, 0, 0, 800, 600, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    }
    
    
    //----------------------------------------------------------------------------
    //
    // main
    //
    
    #ifdef _WIN32
    int CALLBACK WinMain(
      _In_ HINSTANCE hInstance,
      _In_ HINSTANCE hPrevInstance,
      _In_ LPSTR     lpCmdLine,
      _In_ int       nCmdShow
    )
    #else
    int
    main( int argc, char** argv )
    #endif
    {
        // Initialize GLFW
        glfwInit();
    
        //TODO Create Windows Class
        // Create Window Hints
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
        glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
        // Create Windows
        GLFWwindow* window = glfwCreateWindow(800, 600, "Triangles", NULL, NULL);
        GLFWwindow* window2 = glfwCreateWindow(800, 600, "Triangles2", NULL, window);
        GLFWwindow* window3 = glfwCreateWindow(800, 600, "Triangles3", NULL, window);
        GLFWwindow* window4 = glfwCreateWindow(800, 600, "Triangles4", NULL, window);
    
        // Set window positions
        glfwSetWindowPos(window,  100, 100);
        glfwSetWindowPos(window2, 900, 100);
        glfwSetWindowPos(window3, 1700, 100);
        glfwSetWindowPos(window4, 2500, 100);
    
        // Initialize Windows
        glfwMakeContextCurrent(window);
        // Initialize gl3w - thanks @Ripi2 for the assist
        gl3wInit();
        init();
        glfwMakeContextCurrent(window2);
        init2(1);
        glfwMakeContextCurrent(window3);
        init2(2);
        glfwMakeContextCurrent(window4);
        init2(3);
    
    
        // Draw the Windows
        while (!glfwWindowShouldClose(window) && !glfwWindowShouldClose(window2) && !glfwWindowShouldClose(window3) && !glfwWindowShouldClose(window4))
        {
            glfwMakeContextCurrent(window);
            display();
            glfwSwapBuffers(window);
            glfwPollEvents();
            glFinish();
    
            glfwMakeContextCurrent(window2);
            display2(1);
            glfwSwapBuffers(window2);
            glfwPollEvents();
            glFinish();
    
            glfwMakeContextCurrent(window3);
            display2(2);
            glfwSwapBuffers(window3);
            glfwPollEvents();
    
            glfwMakeContextCurrent(window4);
            display2(3);
            glfwSwapBuffers(window4);
            glfwPollEvents();
        }
    
    
        // Destroy Windows
        glfwDestroyWindow(window);
        glfwDestroyWindow(window2);
        glfwDestroyWindow(window3);
        glfwDestroyWindow(window4);
    
    
        // Terminate GLFW Instance
        glfwTerminate();
    }
    

    The Final Render

    enter image description here