timekeeper
Topic Author
Posts: 3
Joined: 06 Jan 2021, 16:17

NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO

22 Mar 2021, 23:53

Hi, I'm a novice here. I'm having some trouble integrating NoesisGUI with C++ and OpenGL on Windows.
With the help of the Integration Tutorial, I managed to get some simple XAML displayed in an OpenGL window created with GLFW. Everything works fine when I draw directly to the screen.
But when I try to draw to a framebuffer and then display that framebuffer on the screen, all I can get is a black window.
I tested the whole create FBO -> draw to FBO -> display FBO on screen code with GL functions, and everything works as expected. But when I use that same FBO as a target for NoesisGUI, the output turns black.
Below is my test code (please, forgive rough style and way too many global variables).

Flags cond1 to cond5 in main simply turn on and off different parts of the code, so they control what happens in the rendering loop. For example:
* cond1 true --> draw a simple GL triangle directly on screen (OK)
* cond2 and cond5 true --> draw the GL triangle on FBO then draw the FBO on screen as a texture (OK)
* cond3 true --> draw a simple XAML directly on the screen (OK)
* cond4 and cond5 true --> draw XAML to FBO, then FBO to screen as texture (BLACK!!!)

My goal is to render some XAML to a framebuffer with NoesisGUI, then use that framebuffer as a texture for further processing. I changed my code again and again, but I couldn't make it work.
What I am doing wrong? What should I do instead?
Thanks for any help
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <NsGui/IView.h>
#include <NsGui/IRenderer.h>
#include <NsGui/TextBlock.h>
#include <NsGui/Storyboard.h>
#include <NsApp/ThemeProviders.h>
#include <NsGui/FontProperties.h>
#include <NsGui/Grid.h>
#include <NsGui/IntegrationAPI.h>
#include <NsRender/GLFactory.h>
#include <NsRender/GLRenderDeviceApi.h>

#include <iostream>
#include <cstdio>
#include "shader.h"

void cbFramebufferSize(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void myNoesisRender(unsigned int targetFbo);
void myNoesisInit();
void myCreateFramebuffer();
void mySetupGlfw();
void mySetupTriangle();
void mySetupQuadForTexture();
void myDrawTriangle(Shader* pShader, unsigned int targetFbo);
void myDrawTextureFromFboOnScreen(Shader* pShader);

const unsigned int SCR_WIDTH = 960;
const unsigned int SCR_HEIGHT = 540;

static Noesis::IView* _view;
static GLFWwindow* pWindow;
unsigned int fbo;
unsigned int triangleVBO, triangleVAO;
unsigned int quadVAO, quadVBO;
unsigned int textColorBuffer;
int numFrames = 0;

// vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates
float quadVertices[] = {
// positions   // texCoords
-1.0f,  1.0f,  0.0f, 1.0f,
-1.0f, -1.0f,  0.0f, 0.0f,
 1.0f, -1.0f,  1.0f, 0.0f,

-1.0f,  1.0f,  0.0f, 1.0f,
 1.0f, -1.0f,  1.0f, 0.0f,
 1.0f,  1.0f,  1.0f, 1.0f
};
int numQuadVertices = sizeof(quadVertices) / (sizeof(float) * 4);

float triangleVertices[] = {
	// positions         // colors
	 0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f, 0.33f,   // bottom right
	-0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f, 0.66f,   // bottom left
	 0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f, 0.99f    // top 
};
int numTriangleVertices = sizeof(triangleVertices) / (sizeof(float) * 7);

//====================================================================================
int main()
{
	mySetupGlfw();

	glEnable(GL_STENCIL_TEST);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);

	// Create shaders
	Shader quadShader("quad.vs", "quad.fs");
	Shader triangleShader("triangle.vs", "triangle.fs");

	mySetupTriangle();
	mySetupQuadForTexture();
	myCreateFramebuffer();

	myNoesisInit();

	// Drawing conditions
	bool cond1 = true;
	bool cond2 = false;
	bool cond3 = false;
	bool cond4 = false;
	bool cond5 = false;

	while (!glfwWindowShouldClose(pWindow)) {
		processInput(pWindow);

		if (cond1) myDrawTriangle(&triangleShader, 0);

		if (cond2) myDrawTriangle(&triangleShader, fbo);

		if (cond3) myNoesisRender(0);

		if (cond4) myNoesisRender(fbo);

		if (cond5) myDrawTextureFromFboOnScreen(&quadShader);

		// Done drawing
		glfwSwapBuffers(pWindow);
		glfwPollEvents();
	}

	glfwTerminate();
	return 0;
}

//----------------------------------------------------------------------------------------
void myNoesisRender(unsigned int targetFramebuffer)
{
	// Update view (layout, animations, ...)
	double elapsedTimeSecs = glfwGetTime();
	_view->Update(elapsedTimeSecs);

	// Offscreen rendering phase populates textures needed by the on-screen rendering
	_view->GetRenderer()->UpdateRenderTree();
	_view->GetRenderer()->RenderOffscreen();

	// If you are going to render here with your own engine you need to restore the GPU state
	// because noesis changes it. In this case only framebuffer and viewport need to be restored

	glBindFramebuffer(GL_FRAMEBUFFER, targetFramebuffer);
	int w, h;
	glfwGetFramebufferSize(pWindow, &w, &h);
	glViewport(0, 0, w, h);

	glClearColor(0.20f, 0.80f, 0.0f, 0.6f);
	glClearStencil(0);
	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Rendering is done in the active framebuffer
	_view->GetRenderer()->Render();
}

//--------------------------------------------------------------------------------------
void myNoesisInit() {
	Noesis::SetLogHandler(
		[](const char*, uint32_t, uint32_t level, const char*, const char* msg) {
			// [TRACE] [DEBUG] [INFO] [WARNING] [ERROR]
			const char* prefixes[] = { "T", "D", "I", "W", "E" };
			printf("[NOESIS/%s] %s\n", prefixes[level], msg);
		});

	// Noesis initialization. This must be the first step before using any NoesisGUI functionality
	Noesis::GUI::Init(NS_LICENSE_NAME, NS_LICENSE_KEY);

	// Setup theme
	NoesisApp::SetThemeProviders();
	Noesis::GUI::LoadApplicationResources("Theme/NoesisTheme.DarkBlue.xaml");

	Noesis::Ptr<Noesis::FrameworkElement> xaml(Noesis::GUI::ParseXaml<Noesis::FrameworkElement>(R"(
		<Grid
		  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
			<Rectangle Width="200" Height="100" Fill="Red"/>
		</Grid>
	)"));

	// View creation to render and interact with the user interface.
	// We transfer the ownership to a global pointer instead of a Ptr<> because there is no way
	// in GLUT to do shutdown and we don't want the Ptr<> to be released at global time.
	_view = Noesis::GUI::CreateView(xaml).GiveOwnership();
	_view->SetFlags(Noesis::RenderFlags_PPAA | Noesis::RenderFlags_LCD);
	_view->SetSize(SCR_WIDTH, SCR_HEIGHT);

	// Renderer initialization with an OpenGL device
	_view->GetRenderer()->Init(NoesisApp::GLFactory::CreateDevice(false));
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* pWwindow)
{
	if (glfwGetKey(pWwindow, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
		glfwSetWindowShouldClose(pWwindow, true);
	}
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void cbFramebufferSize(GLFWwindow* pWnd, int w, int h)
{
	// make sure the viewport matches the new window dimensions; note that width and 
	// height will be significantly larger than specified on retina displays.
	glViewport(0, 0, w, h);
	_view->SetSize(w, h);
}

//---------------------------------------------------------------
void myCreateFramebuffer()
{
	glGenFramebuffers(1, &fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);

	glGenTextures(1, &textColorBuffer);
	glBindTexture(GL_TEXTURE_2D, textColorBuffer);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, 0);

	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textColorBuffer, 0);

	unsigned int rbo;
	glGenRenderbuffers(1, &rbo);
	glBindRenderbuffer(GL_RENDERBUFFER, rbo);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);

	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
		printf("Framebuffer is not complete!\n");
	}

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

//----------------------------------------------------------------------
void mySetupGlfw()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE);
	glfwWindowHint(GLFW_DECORATED, GLFW_TRUE);

	pWindow = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Demo", nullptr, nullptr);
	if (pWindow == nullptr) {
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return;
	}
	glfwSetWindowPos(pWindow, 940, 40);

	glfwMakeContextCurrent(pWindow);
	glfwSetFramebufferSizeCallback(pWindow, cbFramebufferSize);

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return;
	}

	glfwSwapInterval(1);
}

//------------------------------------------------------------------------
void mySetupTriangle() {
	glGenVertexArrays(1, &triangleVAO);
	glGenBuffers(1, &triangleVBO);
	glBindVertexArray(triangleVAO);
	glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(triangleVertices), triangleVertices, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);
}

//------------------------------------------------------------------------
void mySetupQuadForTexture() {
	glGenVertexArrays(1, &quadVAO);
	glGenBuffers(1, &quadVBO);
	glBindVertexArray(quadVAO);
	glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
}

//------------------------------------------------------------------------
void myDrawTriangle(Shader* pShader, unsigned int targetFbo) {
	glBindFramebuffer(GL_FRAMEBUFFER, targetFbo);

	glClearColor(0.4f, 0.3f, 0.3f, 0.0f);
	glClearStencil(0);
	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	pShader->use();
	glBindVertexArray(triangleVAO);
	glDisable(GL_DEPTH_TEST);
	glBindTexture(GL_TEXTURE_2D, textColorBuffer);
	glDrawArrays(GL_TRIANGLES, 0, numTriangleVertices);
}

//------------------------------------------------------------------------
void myDrawTextureFromFboOnScreen(Shader* pShader) {
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glClearColor(0.5f, 1.0f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	int w, h;
	glfwGetFramebufferSize(pWindow, &w, &h);
	glViewport(0, 0, w, h);

	pShader->use();
	glBindVertexArray(quadVAO);
	glDisable(GL_DEPTH_TEST);
	glBindTexture(GL_TEXTURE_2D, textColorBuffer);
	glDrawArrays(GL_TRIANGLES, 0, numQuadVertices);
}
#pragma once

#include <glad/glad.h>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader
{
public:
    unsigned int ID;
    // constructor generates the shader on the fly
    // ------------------------------------------------------------------------
    Shader(const char* vertexPath, const char* fragmentPath)
    {
        // 1. retrieve the vertex/fragment source code from filePath
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        // ensure ifstream objects can throw exceptions:
        vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try {
            // open files
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);
            std::stringstream vShaderStream, fShaderStream;
            // read file's buffer contents into streams
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            // close file handlers
            vShaderFile.close();
            fShaderFile.close();
            // convert stream into string
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        } catch (std::ifstream::failure& e) {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
        }
        const char* vShaderCode = vertexCode.c_str();
        const char* fShaderCode = fragmentCode.c_str();
        // 2. compile shaders
        unsigned int vertex, fragment;
        // vertex shader
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        checkCompileErrors(vertex, "VERTEX");
        // fragment Shader
        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        checkCompileErrors(fragment, "FRAGMENT");
        // shader Program
        ID = glCreateProgram();
        glAttachShader(ID, vertex);
        glAttachShader(ID, fragment);
        glLinkProgram(ID);
        checkCompileErrors(ID, "PROGRAM");
        // delete the shaders as they're linked into our program now and no longer necessary
        glDeleteShader(vertex);
        glDeleteShader(fragment);
    }
    // activate the shader
    // ------------------------------------------------------------------------
    void use()
    {
        glUseProgram(ID);
    }
    // utility uniform functions
    // ------------------------------------------------------------------------
    void setBool(const std::string& name, bool value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
    }
    // ------------------------------------------------------------------------
    void setInt(const std::string& name, int value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    }
    // ------------------------------------------------------------------------
    void setFloat(const std::string& name, float value) const
    {
        glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
    }

private:
    // utility function for checking shader compilation/linking errors.
    // ------------------------------------------------------------------------
    void checkCompileErrors(unsigned int shader, std::string type)
    {
        int success;
        char infoLog[1024];
        if (type != "PROGRAM") {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success) {
                glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        } else {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success) {
                glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
    }
};
 
User avatar
jsantos
Site Admin
Posts: 3918
Joined: 20 Jan 2012, 17:18
Contact:

Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO

24 Mar 2021, 01:55

I don't see any obvious error in you code but this must be something silly because NoesisGUI is able to render to both default framebuffer and FBO. Have you tried capturing a frame with renderdoc?
 
timekeeper
Topic Author
Posts: 3
Joined: 06 Jan 2021, 16:17

Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO

24 Mar 2021, 21:24

Thank you jsantos for looking into my code. I admit I sort of hoped that someone could point to some fairly obvious error in my code. Anyway, I understand from your comment that I am probably not so off track. I'll keep tinkering with the parameters and tell you if (when) I find out what's wrong. Thanks also for suggesting renderdoc, it will be certainly useful in debugging.
 
User avatar
jsantos
Site Admin
Posts: 3918
Joined: 20 Jan 2012, 17:18
Contact:

Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO

25 Mar 2021, 16:21

Thanks, please let us know.
 
timekeeper
Topic Author
Posts: 3
Joined: 06 Jan 2021, 16:17

Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO

26 Mar 2021, 22:38

Problem solved!
With the help of renderdoc and a little patience, I found the error in my code. Or rather, a missing function call.
After calling Noesis rendering function, in myDrawTextureFromFboOnScreen() I need to reset the texture unit to its initial (default) value before drawing the FBO to the screen. This is not needed when rendering to the FBO with simple GL functions, since in that case the texture unit doesn't change.
Something silly, as you guessed... Now it works like a charm.
Thanks
void myDrawTextureFromFboOnScreen(Shader* pShader) {
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glClearColor(0.5f, 1.0f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	int w, h;
	glfwGetFramebufferSize(pWindow, &w, &h);
	glViewport(0, 0, w, h);

	pShader->use();

	// After calling Noesis functions, texture unit must be reset to its default value.
	// This is not needed when drawing with simple GL functions.
	glActiveTexture(GL_TEXTURE0);

	glBindTexture(GL_TEXTURE_2D, textColorBuffer);
	glBindVertexArray(quadVAO);
	glDisable(GL_DEPTH_TEST);
	glDrawArrays(GL_TRIANGLES, 0, numQuadVertices);
}
 
User avatar
jsantos
Site Admin
Posts: 3918
Joined: 20 Jan 2012, 17:18
Contact:

Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO

29 Mar 2021, 20:37

Great! Glad to know you solved it. Thanks for the feedback. 😄

Who is online

Users browsing this forum: Ahrefs [Bot], DHSven, Google [Bot], maherne and 35 guests