- timekeeper
- Posts: 3
- Joined:
NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO
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;
}
}
}
};
Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO
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
- Posts: 3
- Joined:
Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO
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.
Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO
Thanks, please let us know.
- timekeeper
- Posts: 3
- Joined:
Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO
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
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
Code: Select all
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);
}
Re: NoesisGUI with C++, OpenGL and GLFW in Windows. Problem with FBO
Great! Glad to know you solved it. Thanks for the feedback. 😄
Who is online
Users browsing this forum: Bing [Bot] and 56 guests