GLFW and Dear ImGui
Last time I tried SDL and Dear ImGui and that was quite interesting. So today I would like to explore a GLFW and Dear ImGui combo.
There will be also a silly SDL vs GLFW comparison.
GLFW is almost the same thing as SDL - a way to create windows and OpenGL contexts. It also seems to have most of the SDL features as well, such as events, user input with keyboard and mouse, and others.
So let’s create a cross-platform application using GLFW and add a Dear ImGui to it.
CMakeLists.txt
It looks like I switched to CMake for good, so I’ll be using it for all my C++ projects, including this one.
GLFW
If you’re on Mac OS or Windows, simply download the pre-build binaries.
If you’re on Linux, then you can try to install it via your package manager, but for me that option failed, so I built it from sources (which can be downloaded from the same page):
$ cd ~/Downloads/glfw-3.3.2
$ mkdir build && cd $_
$ nano ../CMakeLists.txt
// you might want to change install path to something else
set(CMAKE_INSTALL_PREFIX "/home/YOUR-NAME/programs/glfw")
$ cmake -DCMAKE_BUILD_TYPE=Release ..
If that fails with the following error:
CMake Error at CMakeLists.txt:208 (message):
RandR headers not found; install libxrandr development package
Install these packages and try again:
$ sudo apt install xorg-dev libglu1-mesa-dev
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ cmake --build . --target install
...
Install the project...
-- Install configuration: "Release"
-- Installing: /home/YOUR-NAME/programs/glfw/include/GLFW
-- Installing: /home/YOUR-NAME/programs/glfw/include/GLFW/glfw3native.h
-- Installing: /home/YOUR-NAME/programs/glfw/include/GLFW/glfw3.h
-- Installing: /home/YOUR-NAME/programs/glfw/lib/cmake/glfw3/glfw3Config.cmake
-- Installing: /home/YOUR-NAME/programs/glfw/lib/cmake/glfw3/glfw3ConfigVersion.cmake
-- Installing: /home/YOUR-NAME/programs/glfw/lib/cmake/glfw3/glfw3Targets.cmake
-- Installing: /home/YOUR-NAME/programs/glfw/lib/cmake/glfw3/glfw3Targets-noconfig.cmake
-- Installing: /home/YOUR-NAME/programs/glfw/lib/pkgconfig/glfw3.pc
-- Installing: /home/YOUR-NAME/programs/glfw/lib/libglfw3.a
Now back to your project. Add GLFW library and its headers:
find_library(GLFW glfw3 PATHS "/home/YOUR-NAME/programs/glfw/lib")
include_directories(
"/home/YOUR-NAME/programs/glfw/include"
)
# ...
target_link_libraries(${CMAKE_PROJECT_NAME}
${GLFW}
)
glad
Once again, in order to be able to use the latest OpenGL version, you will need an OpenGL loader library, one of which is glad.
I already described how to get it in the previous article about SDL.
Dear ImGui
Like the last time Dear ImGui source code files are added as they are:
set(sources
main.cpp
functions.cpp
imgui/imconfig.h
imgui/imgui_demo.cpp
imgui/imgui_draw.cpp
imgui/imgui_impl_glfw.cpp
imgui/imgui_impl_glfw.h
imgui/imgui_impl_opengl3.cpp
imgui/imgui_impl_opengl3.h
imgui/imgui_internal.h
imgui/imgui_widgets.cpp
imgui/imgui.cpp
imgui/imgui.h
imgui/imstb_rectpack.h
imgui/imstb_textedit.h
imgui/imstb_truetype.h
)
The only difference is that this time instead of SDL implementation we’re using GLFW implementation.
System libraries
On all the platforms you’ll need the OpenGL package:
find_package(OpenGL REQUIRED)
# ...
target_link_libraries(${CMAKE_PROJECT_NAME}
${OPENGL_gl_LIBRARY}
)
Windows
Nothing else is required on Windows. Seriously, it’s not the first time when some development-related thing goes smoothly on Windows, while on other platforms it is accompanied with all sorts of brainfuck. Makes you think, you know.
Well, actually there was one thing. At first building was failing with complains about main()
function not being WinMain()
, which I resolved with the following instruction:
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
But when I removed it later at some point, nothing changed - there were no more complains and it all worked fine with main()
.
Mac OS
On Mac OS the following frameworks are required:
target_link_libraries(${CMAKE_PROJECT_NAME}
"-framework Cocoa"
"-framework IOKit"
"-framework CoreVideo"
)
Without it you’ll get a kilometer-long list of linking errors like this:
[build] Undefined symbols for architecture x86_64:
[build] "_CFArrayAppendValue", referenced from:
[build] __glfwInitJoysticksNS in libglfw3.a(cocoa_joystick.m.o)
[build] _matchCallback in libglfw3.a(cocoa_joystick.m.o)
Linux
On Linux you’ll need the following packages:
find_package(Threads REQUIRED)
find_package(X11 REQUIRED)
# ...
target_link_libraries(${CMAKE_PROJECT_NAME}
${CMAKE_THREAD_LIBS_INIT}
${X11_LIBRARIES}
${CMAKE_DL_LIBS}
)
If you won’t add those, you’ll get these linking errors:
[build] /usr/bin/ld: libglfw3.a(posix_thread.c.o): undefined reference to symbol 'pthread_key_delete@@GLIBC_2.2.5'
[build] //lib/x86_64-linux-gnu/libpthread.so.0: error adding symbols: DSO missing from command line
[build] /usr/bin/ld: libglfw3.a(x11_window.c.o): undefined reference to symbol 'XConvertSelection'
[build] //usr/lib/x86_64-linux-gnu/libX11.so.6: error adding symbols: DSO missing from command line
[build] /usr/bin/ld: libglad.a(glad.c.o): undefined reference to symbol 'dlclose@@GLIBC_2.2.5'
[build] //lib/x86_64-linux-gnu/libdl.so.2: error adding symbols: DSO missing from command line
You can find the full CMakeLists.txt
here.
Application
A simple window
Here’s how you get a basic window with GLFW:
// C++
#include <string>
#include <iostream>
// GLFW
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "functions.h"
std::string programName = "GLFW window";
int windowWidth = 1200,
windowHeight = 800;
float backgroundR = 0.1f,
backgroundG = 0.3f,
backgroundB = 0.2f;
static void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
glViewport(0, 0, width, height);
}
void teardown(GLFWwindow *window)
{
if (window != NULL) { glfwDestroyWindow(window); }
glfwTerminate();
}
int main(int argc, char *argv[])
{
if (!glfwInit())
{
std::cerr << "[ERROR] Couldn't initialize GLFW\n";
return -1;
}
else
{
std::cout << "[INFO] GLFW initialized\n";
}
// setup GLFW window
glfwWindowHint(GLFW_DOUBLEBUFFER , 1);
glfwWindowHint(GLFW_DEPTH_BITS, 24);
glfwWindowHint(GLFW_STENCIL_BITS, 8);
glfwWindowHint(
GLFW_OPENGL_PROFILE,
GLFW_OPENGL_CORE_PROFILE
);
std::string glsl_version = "";
#ifdef __APPLE__
// GL 3.2 + GLSL 150
glsl_version = "#version 150";
glfwWindowHint( // required on Mac OS
GLFW_OPENGL_FORWARD_COMPAT,
GL_TRUE
);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
#elif __linux__
// GL 3.2 + GLSL 150
glsl_version = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
#elif _WIN32
// GL 3.0 + GLSL 130
glsl_version = "#version 130";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
#endif
float highDPIscaleFactor = 1.0;
#ifdef _WIN32
// if it's a HighDPI monitor, try to scale everything
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
float xscale, yscale;
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
if (xscale > 1 || yscale > 1)
{
highDPIscaleFactor = xscale;
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
}
#elif __APPLE__
// to prevent 1200x800 from becoming 2400x1600
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
#endif
GLFWwindow *window = glfwCreateWindow(
windowWidth,
windowHeight,
programName.c_str(),
NULL,
NULL
);
if (!window)
{
std::cerr << "[ERROR] Couldn't create a GLFW window\n";
teardown(NULL);
return -1;
}
// watch window resizing
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwMakeContextCurrent(window);
// VSync
glfwSwapInterval(1);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cerr << "[ERROR] Couldn't initialize GLAD" << std::endl;
teardown(window);
return -1;
}
else
{
std::cout << "[INFO] GLAD initialized\n";
}
std::cout << "[INFO] OpenGL from glad "
<< GLVersion.major << "." << GLVersion.minor
<< std::endl;
int actualWindowWidth, actualWindowHeight;
glfwGetWindowSize(window, &actualWindowWidth, &actualWindowHeight);
glViewport(0, 0, actualWindowWidth, actualWindowHeight);
glClearColor(backgroundR, backgroundG, backgroundB, 1.0f);
// --- rendering loop
while (!glfwWindowShouldClose(window))
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
teardown(window);
return 0;
}
Running this on Mac OS gives this simple window:
And the console output:
[INFO] GLFW initialized
[INFO] GLAD initialized
[INFO] OpenGL from glad 4.1
High DPI
What’s interesting here is the way how GLFW allows you to handle High DPI / Retina displays - you can set so-called window creation hints before creating a window.
For example, on Mac OS you can use GLFW_COCOA_RETINA_FRAMEBUFFER hint to prevent your window from getting values twice as big in pixels:
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
And on Windows it’s the other way around - if your window is too damn small, then you might want to set GLFW_SCALE_TO_MONITOR hint:
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
As you can see I also defined a highDPIscaleFactor
variable here for later use.
Adding Dear ImGui
Rendering GUI with Dear ImGui in a GLFW window is not so different from doing the same with SDL, so I won’t place the entire code for it here, you can take a look at it later in the repository. I based my code on this example.
Mostly, the only differences would be GLFW-related Dear ImGui implementations, which come instead of SDL:
//#include "imgui/imgui_impl_sdl.h"
#include "imgui/imgui_impl_glfw.h"
// ...
//ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version.c_str());
// ...
//ImGui_ImplSDL2_NewFrame(window);
ImGui_ImplGlfw_NewFrame();
Note that I used an OpenGL 3.x (imgui/imgui_impl_opengl3.h
) implementation here. This is because documentation recommends not to use OpenGL 2.x (imgui/imgui_impl_opengl2.h
) implementation in actual applications and take the OpenGL 3.x instead. I can only second this suggestion as while this example worked just fine, I ran into certain problems with my other application, and I was able to resolve those only after switching to OpenGL 3.x implementation.
On High DPI displays you might want to utilize highDPIscaleFactor
variable that we set earlier - to scale all the paddings/margins/spacings:
void setImGuiStyle(float highDPIscaleFactor)
{
ImGuiStyle &style = ImGui::GetStyle();
// ...
style.ScaleAllSizes(highDPIscaleFactor);
}
and also the font sizes:
io.Fonts->AddFontFromFileTTF("verdana.ttf", 18.0f * highDPIscaleFactor, NULL, NULL);
It’s cross-platform
Since it’s C++ and thanks to our universal CMakeLists.txt, the application builds and works fine not only on Mac OS but also on Windows:
and on Linux:
The full project source code is published here.
GLFW vs SDL
I cannot really compare GLFW with SDL thoroughly, because I don’t know much about their capabilities, but I was curious to compare the footprint of applications built with both.
Mac OS
Binary size:
- SDL-based: 693 KB;
- GLFW-based: 1.3 MB.
CPU and memory usage:
Linux
Binary size:
- SDL-based: 731 KB;
- GLFW-based: 1.4 MB.
CPU and memory usage:
Windows
Binary size:
- SDL-based: 506 KB (plus 1.26 MB of
SDL2.dll
, which should be less in case of static linking); - GLFW-based: 882 KB.
CPU and memory usage:
Updates
22.01.2022
The article got somewhat obsolete, as the reference application has been updated over the time (starting with this commit):
- CMake project file is better structured now;
- initialization of GLFW and Dear ImGui was moved to functions;
- same for composing the Dear ImGui frame;
- dependencies can be resolved with Conan (more details here);
- other smaller changes and improvements.
Also, thanks to this great article, the scene finally has some content (a triangle):
Social networks
Zuck: Just ask
Zuck: I have over 4,000 emails, pictures, addresses, SNS
smb: What? How'd you manage that one?
Zuck: People just submitted it.
Zuck: I don't know why.
Zuck: They "trust me"
Zuck: Dumb fucks