Created
April 30, 2022 16:13
-
-
Save alecjacobson/fbdc8ea76912589b146cf3eac9bc32cb to your computer and use it in GitHub Desktop.
glfw mini-app to demonstrate how fps counter is lying
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// On mac, first install eigen and glfw | |
// | |
// brew install glfw eigen | |
// | |
// Compile with something like: | |
// | |
// clang++ -O3 -DNDEBUG -o mini-glfw main.cpp -std=c++11 \ | |
// -I /opt/homebrew/include/eigen3 \ | |
// -I /opt/homebrew/include/ -L/opt/homebrew/lib -lglfw -framework OpenGL | |
// | |
// Then run | |
// | |
// ./mini-glfw | |
// | |
// | |
// Controls how much (dummy) computation happens in the vertex shader. | |
const int m = 20000; | |
#define GL_SILENCE_DEPRECATION | |
#include <OpenGL/gl3.h> | |
#define __gl_h_ | |
#include <Eigen/Core> | |
#include <Eigen/Geometry> | |
#define GLFW_INCLUDE_GLU | |
#include <GLFW/glfw3.h> | |
#include <chrono> | |
#include <string> | |
#include <chrono> | |
#include <thread> | |
#include <iostream> | |
std::string vertex_shader = R"( | |
#version 330 core | |
uniform mat4 proj; | |
uniform mat4 model; | |
uniform float t; | |
uniform int m; | |
in vec3 position; | |
out vec4 position_eye; | |
void main() | |
{ | |
vec4 deformed = | |
vec4( | |
position.x, | |
position.y, | |
sin(t*3.14159)* | |
cos(position.x*3.14159)* | |
cos(position.y*3.14159) | |
, | |
1.); | |
for(int j = 0;j<m;j++) | |
{ | |
deformed.z = deformed.z + 0.000001*float(j)/float(m); | |
} | |
position_eye = proj * model * deformed; | |
gl_Position = position_eye; | |
} | |
)"; | |
std::string fragment_shader = R"( | |
#version 330 core | |
in vec4 position_eye; | |
out vec3 color; | |
void main() | |
{ | |
vec3 xTangent = dFdx(position_eye.xyz); | |
vec3 yTangent = dFdy(position_eye.xyz); | |
color = normalize( cross( yTangent, xTangent ) )*0.5 + 0.5; | |
} | |
)"; | |
// width, height, shader id, vertex array object | |
int w=800,h=600; | |
double highdpi=1; | |
GLuint prog_id=0; | |
GLuint VAO; | |
// Mesh data: RowMajor is important to directly use in OpenGL | |
Eigen::Matrix< float,Eigen::Dynamic,3,Eigen::RowMajor> V; | |
Eigen::Matrix<GLuint,Eigen::Dynamic,3,Eigen::RowMajor> F; | |
int main(int argc, char * argv[]) | |
{ | |
using namespace std; | |
const auto get_seconds = []() | |
{ | |
return | |
std::chrono::duration<double>( | |
std::chrono::system_clock::now().time_since_epoch()).count(); | |
}; | |
if(!glfwInit()) | |
{ | |
cerr<<"Could not initialize glfw"<<endl; | |
return EXIT_FAILURE; | |
} | |
const auto & error = [] (int error, const char* description) | |
{ | |
cerr<<description<<endl; | |
}; | |
glfwSetErrorCallback(error); | |
glfwWindowHint(GLFW_SAMPLES, 4); | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); | |
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | |
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); | |
GLFWwindow* window = glfwCreateWindow(w, h, "WebGL", NULL, NULL); | |
if(!window) | |
{ | |
glfwTerminate(); | |
cerr<<"Could not create glfw window"<<endl; | |
return EXIT_FAILURE; | |
} | |
glfwMakeContextCurrent(window); | |
int major, minor, rev; | |
major = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR); | |
minor = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR); | |
rev = glfwGetWindowAttrib(window, GLFW_CONTEXT_REVISION); | |
printf("OpenGL version recieved: %d.%d.%d\n", major, minor, rev); | |
printf("Supported OpenGL is %s\n", (const char*)glGetString(GL_VERSION)); | |
printf("Supported GLSL is %s\n", (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); | |
glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_NORMAL); | |
const auto & reshape = [] (GLFWwindow* window, int w, int h) | |
{ | |
::w=w,::h=h; | |
}; | |
glfwSetWindowSizeCallback(window,reshape); | |
{ | |
int width, height; | |
glfwGetFramebufferSize(window, &width, &height); | |
int width_window, height_window; | |
glfwGetWindowSize(window, &width_window, &height_window); | |
highdpi = width/width_window; | |
reshape(window,width_window,height_window); | |
} | |
// Compile each shader | |
const auto & compile_shader = [](const GLint type,const char * str) -> GLuint | |
{ | |
GLuint id = glCreateShader(type); | |
glShaderSource(id,1,&str,NULL); | |
glCompileShader(id); | |
return id; | |
}; | |
GLuint vid = compile_shader(GL_VERTEX_SHADER,vertex_shader.c_str()); | |
GLuint fid = compile_shader(GL_FRAGMENT_SHADER,fragment_shader.c_str()); | |
// attach shaders and link | |
prog_id = glCreateProgram(); | |
glAttachShader(prog_id,vid); | |
glAttachShader(prog_id,fid); | |
glLinkProgram(prog_id); | |
GLint status; | |
glGetProgramiv(prog_id, GL_LINK_STATUS, &status); | |
glDeleteShader(vid); | |
glDeleteShader(fid); | |
// construct a regular grid mesh | |
const int nx = 300; | |
const int ny = 305; | |
V.resize(nx*ny,3); | |
for(int i = 0;i<nx;i++) | |
{ | |
for(int j = 0;j<ny;j++) | |
{ | |
const float x = float(i)/(nx-1); | |
const float y = float(j)/(ny-1); | |
V.row(j*nx+i) << x,y, 0; | |
} | |
} | |
F.resize((nx-1)*(ny-1)*2,3); | |
for(int y = 0;y<ny-1;y++) | |
{ | |
for(int x = 0;x<nx-1;x++) | |
{ | |
// index of southwest corner | |
const int sw = (x +nx*(y+0)); | |
const int se = (x+1+nx*(y+0)); | |
const int ne = (x+1+nx*(y+1)); | |
const int nw = (x +nx*(y+1)); | |
// Index of first triangle in this square | |
const int gf = 2*(x+(nx-1)*y); | |
F(gf+0,0) = sw; | |
F(gf+0,1) = se; | |
F(gf+0,2) = nw; | |
F(gf+1,0) = se; | |
F(gf+1,1) = ne; | |
F(gf+1,2) = nw; | |
} | |
} | |
V.rowwise() -= V.colwise().mean(); | |
V /= (V.colwise().maxCoeff()-V.colwise().minCoeff()).maxCoeff(); | |
V /= 1.2; | |
// Generate and attach buffers to vertex array | |
glGenVertexArrays(1, &VAO); | |
GLuint VBO, EBO; | |
glGenBuffers(1, &VBO); | |
glGenBuffers(1, &EBO); | |
glBindVertexArray(VAO); | |
glBindBuffer(GL_ARRAY_BUFFER, VBO); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*V.size(), V.data(), GL_STATIC_DRAW); | |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); | |
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*F.size(), F.data(), GL_STATIC_DRAW); | |
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); | |
glEnableVertexAttribArray(0); | |
glBindBuffer(GL_ARRAY_BUFFER, 0); | |
glBindVertexArray(0); | |
double t0 = get_seconds(); | |
const auto draw = [&]() | |
{ | |
double tic = get_seconds(); | |
// clear screen and set viewport | |
glClearColor(0.1,0.1,0.1,0.); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
glViewport(0,0,w*highdpi,h*highdpi); | |
// Projection and modelview matrices | |
Eigen::Matrix4f proj; | |
float near = 0.01; | |
float far = 100; | |
float top = tan(35./360.*M_PI)*near; | |
float right = top * (double)::w/(double)::h; | |
float left = -right; | |
float bottom = -top; | |
proj.setConstant(4,4,0.); | |
proj(0,0) = (2.0 * near) / (right - left); | |
proj(1,1) = (2.0 * near) / (top - bottom); | |
proj(0,2) = (right + left) / (right - left); | |
proj(1,2) = (top + bottom) / (top - bottom); | |
proj(2,2) = -(far + near) / (far - near); | |
proj(3,2) = -1.0; | |
proj(2,3) = -(2.0 * far * near) / (far - near); | |
Eigen::Affine3f model = Eigen::Affine3f::Identity(); | |
model.translate(Eigen::Vector3f(0,0,-1.5)); | |
// select program and attach uniforms | |
glUseProgram(prog_id); | |
GLint proj_loc = glGetUniformLocation(prog_id,"proj"); | |
glUniformMatrix4fv(proj_loc,1,GL_FALSE,proj.data()); | |
GLint model_loc = glGetUniformLocation(prog_id,"model"); | |
glUniformMatrix4fv(model_loc,1,GL_FALSE,model.matrix().data()); | |
GLint t_loc = glGetUniformLocation(prog_id,"t"); | |
glUniform1f(t_loc,tic-t0); | |
GLint m_loc = glGetUniformLocation(prog_id,"m"); | |
glUniform1i(m_loc,m); | |
// Draw mesh as wireframe | |
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); | |
glBindVertexArray(VAO); | |
glDrawElements(GL_TRIANGLES, F.size(), GL_UNSIGNED_INT, 0); | |
glBindVertexArray(0); | |
}; | |
// Main display routine | |
while (!glfwWindowShouldClose(window)) | |
{ | |
double tic = get_seconds(); | |
static size_t count = 0; | |
static double t_prev = get_seconds(); | |
if(tic-t_prev > 1) | |
{ | |
const double fps = double(count)/(tic-t_prev); | |
std::stringstream ss; | |
ss << fps <<" FPS"; | |
glfwSetWindowTitle(window, ss.str().c_str()); | |
count = 0; | |
t_prev = tic; | |
} | |
count++; | |
draw(); | |
glfwSwapBuffers(window); | |
{ | |
glfwPollEvents(); | |
//// In microseconds | |
//double duration = 1000000.*(get_seconds()-tic); | |
//const double min_duration = 1000000./60.; | |
//if(duration<min_duration) | |
//{ | |
// std::this_thread::sleep_for(std::chrono::microseconds((int)(min_duration-duration))); | |
//} | |
} | |
} | |
glfwDestroyWindow(window); | |
glfwTerminate(); | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment