NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO
Posted: 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
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
Code: Select all
#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);
}
Code: Select all
#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;
}
}
}
};