I'm writing a text editor using OpenGL and GLFW and traditionally GUI applications scale from top left-corner (instead of from bottom-left corner), so that's why this is important to me.
I want to make it so when I resize window, rendered stuff goes top-left instead of bottom-left.
Here is some screenshots to show what i mean:
Initial window:
Resized window (goes to bottom left):
How I want resized window to look (top left):
Minimal reproducible example:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
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_SUCCESSFULLY_READ: " << e.what() << 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;
}
}
}
};
void processInput(GLFWwindow *window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
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
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
Shader ourShader("3.3.shader.vs", "3.3.shader.fs"); // you can name your shader files however you like
float vertices[] = {
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ourShader.use();
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
3.3.shader.fs:
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0f);
}
3.3.shader.vs:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
}
First of all, from the question and comments, I suspect that this is an XY problem. Regardless, let's address the actual question and code first.
Curiously missing from the mre in the question is the setting of the viewport. Commonly (for example in the Learn OpenGL tutorials and the GLFW documentation) the viewport is set using a callback function. When the framebuffer of the window is resized, the viewport is then changed accordingly:
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
This is typical OpenGL usage: the positions of the primitives are set in the vertex shader to gl_Position
in clip space (in the range [-1, 1]), then converted to Normalized Device Coordinates (NDCs) and using the viewport parameters, these are converted to window coordinates. When the window is resized, the triangle remains in the middle and gets stretched/compressed as the relative distances to the edges remain the same.
In the given mre, this doesn't happen. The viewport is set to the (suggested) size of the window (600x800), as this happens by default, and this size is used even if the window/framebuffer is resized. As a result, the triangle will always render with a fixed size and with a fixed distance to the origin (bottom-left) in window coordinates.
For as far as I know, it's not possible to change this origin to top-left, but we can look at some options that come close. It is possible to do this for clip space:
glClipControl(GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE); // Requires OpenGL >=4.5
But since this is for clip space and not window space, the triangle would merely appear upside down and would still be in the bottom-left corner.
Your suggestion in the comments doesn't work either:
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, height - SCR_HEIGHT, SCR_WIDTH, SCR_HEIGHT);
}
When height > SCR_HEIGHT
, the second parameter becomes negative, which is not allowed (GL_INVALID_VALUE
). Note that providing a negative width and height for the third and fourth parameters is not possible either; these parameters are unsigned integers.
Now looking at your second suggestion:
void framebuffer_size_callback(GLFWwindow *window, int width, int height) { src_height = height; }
and in [the]
RenderText
function [edit this]:float ypos = y - (ch.Size.y - ch.Bearing.y) * scale;
to this:float ypos = src_height - (y + (ch.Size.y - ch.Bearing.y) * scale);
This refers to code outside of the mre, so I cannot be sure, but it looks like this would amount to making the characters' y-position depend on the window height. This could provide the effect you're looking for, but the exact results would vary from machine to machine (keep in mind that the dimensions passed to glfwCreateWindow
are merely hints!) and it would still be a highly unusual way of using OpenGL. OpenGL is typically used to render a scene, defined in some world space, by means of a projection and other transformations - agnostic of the actual window resolution.
Let's go back to your actual goal: writing a text editor that places text in the top left corner. The usual OpenGL approach would be this:
glfwCreateWindow(..)
. Alternatively, use the callback function to store the framebuffer size and set the viewport just before the rendering.The result should be that the objects (characters/text) in your scene always appear at the same relative position, independent of the resolution.