Brief


A header-only library that gets you a semi-customizable window and an OpenGL context, minimizing dependencies and hiding the complexities of doing proper windowing on each platform.

It's written with performance in mind; though performance is not the main focus of it. Maintainability, support for the defined feature set and ease of use are the primary targets of this library.

This library can:

  • Open a window, handle input from keyboard/mouse/controller
  • Draw while resizing the window
  • Disable window decorations, but still be able to tile the window, customize the drag area, etc...
  • Load OpenGL (without using a loader library)
  • Set the window icon
  • All of that, with as minimal dependencies as possible (for example, you don't even need to link to or have the development headers/libraries of X11/OpenGL, etc. on linux)

The main idea of this library is that you shouldn't need to think about installing CMake, or messing with your build tool's link paths, just to be able to draw some stuff using the GPU. It should be easy to just drop in right inside of your project, and start developing, with full access to a relatively small source code size.

Additionally, this library:

  • Provides custom implementations of several math functions, like sinf
  • Doesn't use the standard C library (where possible)
  • Prodives convenience functions that can, for example, construct a perpsective projection matrix for OpenGL
  • Automatically sets up OpenGL debugging infrastructure

Why should you use this library over, GLFW, SDL, SFML, ...?
Those libraries solve a much more general problem than the one most developers have:

  • Create one or more windows
  • Handle child/parent window relationships
  • Provide aliases for common usermode operations (show file dialog, message box)
  • Support many more platforms, even ones that don't include a windowing system.
  • Support multiple graphics APIs for initialization.
  • Provide a high level drawing interface.
  • Provide a high level sound interface.
  • Allow for entirely customized windows.
  • And much more...
But in their attempt to be "generic" and "robust", they fail to provide a good and simple interface that covers 90% of the use cases, and is implemented in the best way possible for each platform.

One common example is that almost every popular windowing library on Windows does not allow the developer to draw while resizing or moving the window. This is an introcacy of the Win32 API (and actually the same is true for MacOS), and the recommended way is not exactly obvious to a new developer, or one unfamiliar with the platform.

Here is all you need (and should need) to create a simple window.
By default it creates an OpenGL 3.3 core profile context:


#include "vd_fw.h"                                           // Include library
int main() {
    vd_fw_init(NULL);                                        // Initialize library
    while (vd_fw_running()) {                                // Check if the window is closed & gather events
        glClearColor(0.5f, 0.3f, 0.2f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        vd_fw_swap_buffers();                                // Swap buffers
    }
    return 0;
}

#define VD_FW_IMPL                                           // Include implementation code
#include "vd_fw.h"
    

Linking & Dependencies


This library is distributed as a single, header-only file, thus the following instructions are meant to be considered for your final application/dynamic library.

In general, I make use of the up-to-date platform specific APIs, that should be available with every distribution of said platform's development environment.

Some preprocessor directives to set depending on your needs:

Option Description
VD_FW_VERSION_MAJOR Major version number of the library
VD_FW_VERSION_MINOR Minor version number of the library
VD_FW_VERSION_PATCH Patch version number of the library
VD_FW_VERSION Combined version (major, minor, patch) number of the library
#define VD_FW_STATIC Set to use this library statically.
#define VD_FW_API // (static, extern, ...dllexport) Set to change the function signature (useful for precompiling the library).
#define VD_FW_INL Set to change the function signature (useful for precompiling the library).
#define VD_FW_NO_CRT Options:
  • Set to '1' to disable including C standard library
  • Set to '0' to enable including C standard library
#define VD_FW_SIN Can be predefined to your implementation of sinf. Options:
  • If VD_FW_NO_CRT == 1 it's resolved to libc sinf
  • If VD_FW_NO_CRT == 0 it's resolved to vd_fw_sin (custom implementation).
#define VD_FW_COS Can be predefined to your implementation of cosf. Options:
  • If VD_FW_NO_CRT == 1 it's resolved to libc cosf
  • If VD_FW_NO_CRT == 0 it's resolved to vd_fw_cos (custom implementation).
#define VD_FW_TAN Can be predefined to your implementation of tanf. Options:
  • If VD_FW_NO_CRT == 1 it's resolved to libc tanf
  • If VD_FW_NO_CRT == 0 it's resolved to vd_fw_tan (custom implementation).
#define VD_FW_SQRT Can be predefined to your implementation of sqrtf. Options:
  • If VD_FW_NO_CRT == 1 it's resolved to libc sqrtf
  • If VD_FW_NO_CRT == 0 it's resolved to vd_fw_sqrt (custom implementation).
#define VD_FW_MEMCPY Can be predefined to your implementation of memcpy. Options:
  • If VD_FW_NO_CRT == 1 it's resolved to libc memcpy
  • If VD_FW_NO_CRT == 0 it's resolved to vd_fw_memcpy (custom implementation).
#define VD_FW_MEMSET Can be predefined to your implementation of memset. Options:
  • If VD_FW_NO_CRT == 1 it's resolved to libc memset
  • If VD_FW_NO_CRT == 0 it's resolved to vd_fw_memset (custom implementation).
#define VD_FW_PREFER_DISCRETE_GPU
// Or
#define VD_FW_PREFER_INTEGRATED_GPU
Change the preferred GPU for the OpenGL context.
Win32 Specific
#define VD_FW_WIN32_SUBSYSTEM
// Can be one of the following:
// VD_FW_WIN32_SUBSYSTEM_CONSOLE
// VD_FW_WIN32_SUBSYSTEM_WINDOWS
Change the Subsystem (Windows only).
#define VD_FW_WIN32_NO_LINKER_COMMENTS Disable automatic linker comments (Windows only).

Windows

With MSVC you don't really need to link anything since linker directives in the translation unit that includes vd_fw.h are defined, so that the appropriate libraries and subsystem are used. Additionally, several core libraries are dynamically loaded in vd_fw_init. The only library required during compilation is kernel32.lib. For reference, here are the libraries this library tries to load during vd_fw_init:

  1. User32
  2. OpenGL32
  3. Dwmapi
  4. Gdi32
  5. uxtheme
  6. shell32
  7. winmm

This library tries as much as possible to reduce the amount of code brought in with the (notoriously large) Windows includes. This is done by manually declaring enumerations, preprocessor macros, function signatures, and structures right inside the implementation part of the library. This looks like this:


typedef struct VdFwtagRGBQUAD {
    VdFwBYTE    rgbBlue;
    VdFwBYTE    rgbGreen;
    VdFwBYTE    rgbRed;
    VdFwBYTE    rgbReserved;
} VdFwRGBQUAD;

typedef struct VdFwtagBITMAPINFO {
    VdFwBITMAPINFOHEADER    bmiHeader;
    VdFwRGBQUAD             bmiColors[1];
} VdFwBITMAPINFO, * VdFwLPBITMAPINFO, * VdFwPBITMAPINFO;

#define VD_FW_PROC_ChoosePixelFormat(name) int name(VdFwHDC hdc, const VdFwPIXELFORMATDESCRIPTOR *ppfd)
typedef VD_FW_PROC_ChoosePixelFormat(VdFwProcChoosePixelFormat);
static VdFwProcChoosePixelFormat *VdFwChoosePixelFormat;

#define VD_FW_PROC_CreateBitmap(name) VdFwHBITMAP name(int nWidth, int nHeight, VdFwUINT nPlanes, VdFwUINT nBitCount, const void* lpBits)
typedef VD_FW_PROC_CreateBitmap(VdFwProcCreateBitmap);
static VdFwProcCreateBitmap *VdFwCreateBitmap;
        

The relevant functions are then loaded when you call vd_fw_init:


HMODULE m             = LoadLibraryA("Gdi32.dll");
VdFwChoosePixelFormat     =     (VdFwProcChoosePixelFormat*)GetProcAddress(m, "ChoosePixelFormat");
VdFwCreateBitmap          =          (VdFwProcCreateBitmap*)GetProcAddress(m, "CreateBitmap");
        

Note that all platform specific functions/typedefs/structs are prefixed with 'VdFw' or 'VD_FW_' so as not to induce collisions in cases where you need to include Windows functionality yourself.

To set the subsystem of the application:

  • Windows: #define VD_FW_WIN32_SUBSYSTEM VD_FW_WIN32_SUBSYSTEM_WINDOWS
  • Console: #define VD_FW_WIN32_SUBSYSTEM VD_FW_WIN32_SUBSYSTEM_CONSOLE

If you're on the Windows subsystem, but would still like a console for debugging purposes, this can be done by setting VdFwInitInfo::gl_options::debug_on = 1 when calling vd_fw_init.
This will allocate a console and set-up the relevant gl debug callback for you.

It's highly recommended to #define VD_FW_NO_CRT 1 so that the CRT is removed when linking. This can reduce the final executable size by a factor of 10 (for small applications).

If you'd like to define options by yourself, #define VD_FW_WIN32_NO_LINKER_COMMENTS before including the implementation.


MacOS

For MacOS, it's a bit different. with clang, you must link several frameworks (via -framework framework_name). Additionally, -x objective-c must be used since the Mac APIs use Objective-C.

  1. pthread (compiler flag)
  2. Cocoa
  3. Metal
  4. QuartzCore
  5. CoreGraphics
  6. IOSurface
  7. IOKit
  8. Foundation
  9. OpenGL

OpenGL on MacOS is deprecated, and including it will throw a ton of warnings about each function. Its maximum supported version of OpenGL is 4.1.

To disable those warnings, you can create the following header file and use it before and after including vd_fw.h:


#ifndef DISABLE_CLANG_DEPRECATIONS_H1
#define DISABLE_CLANG_DEPRECATIONS_H1
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif // defined(__clang__)
#else
#if defined(__clang__)
#pragma clang diagnostic pop
#endif // defined(__clang__)
#endif // DISABLE_CLANG_DEPRECATIONS_H1
        

Support



zlib License

(C) Copyright 2025-2026 Michael Dodis (michaeldodisgr@gmail.com)
 This software is provided 'as-is', without any express or implied
warranty.  In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
        

As stated in the license, no warranty or guarantee is provided by the use of this library.

That being said, I will look at issues when I have the time, and/or if they are important to the core principles of this library.

Submit issues here

Platforms

Due to platform limitations, platform-specific APIs and frameworks, vd_fw.h will never have support for some platforms. One example is Android, where Java is required.

Though, I'm still thinking about how this can be done easily, so I will leave the possibility open.

Platform Supported
Windows Yes
MacOS In Progress
Linux (X11) In Progress
Linux (Wayland) Planned
iOS Planned
Android Not Planned
Web Not Planned

Revisions


Attributions

  • CookedNick
    For the render thread with Win32 example in jai
  • idrassi
    For the ported jai code for Win32 example
  • melak47
    For the borderless window with Win32 example
  • TheoBendixson
    For his Handmade Mac platform layer

Tutorials


A collection of tutorials for how to use the library.

These do not show proper code, nor do they show correct OpenGL API usage, but they show various features of vd_fw.h

We will start by creating a simple window, and continue with:

  • Making it borderless
  • Drawing a rectangle
  • Setting the drag area
  • Drawing some 3D geometry
  • Handling input in a user-friendly way
You can safely read each tutorial on its own, or in order. It will guide you through almost all of the parts of the API

Additionally, several samples are available here, which map to several parts of the tutorial.

This tutorial is written in C, but you can safely use C++ with it

Main Loop

Let's look at the main loop.


#define VD_FW_NO_CRT 0                                       // Disable CRT, this is highly recommended,
                                                             // if you know what you're doing.
#define VD_FW_WIN32_SUBSYSTEM VD_FW_WIN32_SUBSYSTEM_WINDOWS  // Disable console (Windows)
#include "vd_fw.h"

int main(int argc, char const *argv[])
{
    vd_fw_init(NULL);                                        // Initialize library
    vd_fw_set_vsync_on(1);                                   // Enable VSYNC

    while (vd_fw_running()) {                                // Check if user closed the window

        int w, h;
        vd_fw_get_size(&w, &h);                              // Get window size, in pixels.

        glViewport(0, 0, w, h);                              // Setup viewport
        glClearColor(0.5f, 0.3f, 0.2f, 1.0f);                // Select clear color
        glClear(GL_COLOR_BUFFER_BIT);                        // Clear

        vd_fw_swap_buffers();                                // Swap buffers
    }

    return 0;
}

#define VD_FW_IMPL                                           // Include implementation
#include "vd_fw.h"

        

This will create a window, that can be moved and resized.


Choose OpenGL Version

To specify the OpenGL version, create a VdFwInitInfo struct, populate the VdFwInitInfo::gl::version member, and pass it to vd_fw_init.


VdFwInitInfo init_info = {};
init_info.gl.version = VD_FW_GL_VERSION_4_5; // Set OpenGL Version
vd_fw_init(&init_info);
        

For reference, here's all of the possible versions:


typedef enum {
    VD_FW_GL_VERSION_BASIC = 0,
    VD_FW_GL_VERSION_1_0   = 1,
    VD_FW_GL_VERSION_1_2   = 12,
    VD_FW_GL_VERSION_1_3   = 13,
    VD_FW_GL_VERSION_1_4   = 14,
    VD_FW_GL_VERSION_1_5   = 15,
    VD_FW_GL_VERSION_2_0   = 20,
    VD_FW_GL_VERSION_2_1   = 21,
    VD_FW_GL_VERSION_3_0   = 30,
    VD_FW_GL_VERSION_3_1   = 31,
    VD_FW_GL_VERSION_3_2   = 32,
    VD_FW_GL_VERSION_3_3   = 33,
    VD_FW_GL_VERSION_4_0   = 40,
    VD_FW_GL_VERSION_4_1   = 41,
    VD_FW_GL_VERSION_4_2   = 42,
    VD_FW_GL_VERSION_4_3   = 43,
    VD_FW_GL_VERSION_4_4   = 44,
    VD_FW_GL_VERSION_4_5   = 45,
    VD_FW_GL_VERSION_4_6   = 46,
} VdFwGlVersion;
        

Create a Borderless Window

To create a borderless window, create a VdFwInitInfo struct, set VdFwInitInfo::window_options::borderless to 1, and pass it to vd_fw_init.


VdFwInitInfo init_info = {};
init_info.window_options.borderless = 1;
vd_fw_init(&init_info);
        

This will create a borderless window, that can be moved (by dragging any part of the window), and resized.


Create a Shader Program

Let's create a simple 2D rectangle application.

We'll create some in-source shaders, and compile them, as we would in any OpenGL application. If you'd like to follow along, here are the shaders we'll be using:


#define VERTEX_SOURCE \
"#version 330 core                                                                                                 \n" \
"layout (location = 0) in vec2 aPos;                                                                               \n" \
"                                                                                                                  \n" \
"uniform vec2 rect_off;                                                                                            \n" \
"uniform vec2 rect_size;                                                                                           \n" \
"uniform mat4 projection;                                                                                          \n" \
"                                                                                                                  \n" \
"                                                                                                                  \n" \
"void main()                                                                                                       \n" \
"{                                                                                                                 \n" \
"    gl_Position = projection * vec4(aPos * rect_size + rect_off, 0.0, 1.0f);                                      \n" \
"}                                                                                                                 \n" \

#define FRAGMENT_SOURCE \
"#version 330 core                                                                                                 \n" \
"out vec4 FragColor;                                                                                               \n" \
"                                                                                                                  \n" \
"uniform vec4 rect_color;                                                                                          \n" \
"                                                                                                                  \n" \
"void main()                                                                                                       \n" \
"{                                                                                                                 \n" \
"    FragColor = rect_color;                                                                                       \n" \
"}                                                                                                                 \n" \
        

And the typical OpenGL shader compilation code:


const char *vertex_shader_source = VERTEX_SOURCE;
const char *fragment_shader_source = FRAGMENT_SOURCE;

// Compile shaders
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_source, 0);
glCompileShader(vertex_shader);

GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_source, 0);
glCompileShader(fragment_shader);

GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
        

In most tutorials, you'll be prompted to do error checking by doing glGetShaderiv, then glGetShaderInfoLog and printing it out...

This library provides a convenience function (and more) to do that, and will also log the errors:


GLuint vertex_shader   = vd_fw_compile_shader(GL_VERTEX_SHADER,   VERTEX_SOURCE);
GLuint fragment_shader = vd_fw_compile_shader(GL_FRAGMENT_SHADER, FRAGMENT_SOURCE);
        

For error logging, make sure to set VdFwInitInfo::gl::debug_on to 1 when initializing the library. Here's the vd_fw_init call we'll be using for the rest of these tutorials:


vd_fw_init(& (VdFwInitInfo) {
    .gl = {
        .version = VD_FW_GL_VERSION_3_3,
        .debug_on = 1,
    },
    .window_options = {
        .borderless = 1,
    }
});
        

Draw a Rectangle

Continuing from the previous section, we create a vertex buffer for a 2d rectangle:


float rect_vertices[] = {
     0.0f,  0.0f,
    +1.0f,  0.0f,
     0.0f, +1.0f,
    +1.0f, +1.0f
};

unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(rect_vertices), rect_vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
        

And now for drawing a rectangle.

We'll use orthographic projection. vd_fw.h provides a simple utility function to compute it: vd_fw_u_ortho


glUseProgram(program);
glBindVertexArray(VAO);

{
    float projection[16];
    vd_fw_u_ortho(0.f, (float)w, (float)h, 0.f, -1.f, 1.f, projection);
    glUniformMatrix4fv(glGetUniformLocation(program, "projection"), 1, GL_FALSE, projection);
}
        

To test that the coordinate transformation is working, we'll map the top left of the rectangle to the mouse. To get the mouse position & buttons, use vd_fw_get_mouse_state or vd_fw_get_mouse_statef:


float mx, my;
vd_fw_get_mouse_statef(&mx, &my);

glUniform4f(glGetUniformLocation(program, "rect_color"), 1.f, 0.f, 0.f, 1.f);
glUniform2f(glGetUniformLocation(program, "rect_size"), 40.f, 40.f);
glUniform2f(glGetUniformLocation(program, "rect_off"), mx, my);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

glUseProgram(0);
glBindVertexArray(0);
        

Set the Drag Area

Let's draw a simple window frame using our rectangle "renderer":


glUniform4f(glGetUniformLocation(program, "rect_color"), 0.2f, 0.2f, 0.2f, 1.f);
glUniform2f(glGetUniformLocation(program, "rect_size"), (float)w, 30.f);
glUniform2f(glGetUniformLocation(program, "rect_off"), 0.f, 0.f);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        

In a borderless window, there is no specified non-client area. So to allow the user to drag the window only from a specific place, we'll use vd_fw_set_ncrects.


int draggable_rect[4] = {
    0,  // left
    0,  // top
    w,  // right
    30, // bottom
};
vd_fw_set_ncrects(draggable_rect, 0, 0);
        

Et voila!

Now, for the 2 last parameters to vd_fw_set_ncrects:

  • count: Number of excluded rects
  • rects: Pointer to array of count rects
These allow you to set areas inside the drag area, that are not draggable.

Let's draw a red 'close' button in our title bar, and if the mouse is over it, darken it:


float button_color[4] = {1.0f, 0.0f, 0.0f, 1.0f};
int mouse_inside_close_button =
    (mx > ((float)w - 30.f)) &&
    (my > (0.f) && my < (30.f));

if (mouse_inside_close_button) {
    button_color[0] = 0.7f;    
    button_color[1] = 0.0f;    
    button_color[2] = 0.0f;    
    button_color[3] = 1.0f;    
}

glUniform4f(glGetUniformLocation(program, "rect_color"), button_color[0],
                                                         button_color[1],
                                                         button_color[2],
                                                         button_color[3]);
glUniform2f(glGetUniformLocation(program, "rect_size"), 30.f, 30.f);
glUniform2f(glGetUniformLocation(program, "rect_off"), (float)w - 30.f, 0.f);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        

As you can see, we can still drag the window even if the mouse is on the button.

To fix that, we have to update our call to vd_fw_set_ncrects:


int draggable_rect[4] = {
    0,  // left
    0,  // top
    w,  // right
    30, // bottom
};

int exclude_rects[1][4] = {
    {
        w - 30, // left
        0,      // top
        w,      // right
        30,     // bottom
    }
};
vd_fw_set_ncrects(draggable_rect, 1, exclude_rects);
        

The code is straightforward; we exclude the exact position of our red button.


Draw a Cube

Let's render some 3D geometry. For this, we'll need separate shaders; if you're following the tutorial in order, here they are:


#define VERTEX_SOURCE3D \
"#version 330 core                                        \n" \
"layout (location = 0) in vec3 aPos;                      \n" \
"layout (location = 1) in vec2 aTexCoord;                 \n" \
"                                                         \n" \
"out vec2 TexCoord;                                       \n" \
"                                                         \n" \
"uniform mat4 projection;                                 \n" \
"uniform mat4 view;                                       \n" \
"                                                         \n" \
"void main()                                              \n" \
"{                                                        \n" \
"    gl_Position = projection * view * vec4(aPos, 1.0f);  \n" \
"    TexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);     \n" \
"}                                                        \n" \

#define FRAGMENT_SOURCE3D \
"#version 330 core                                        \n" \
"out vec4 FragColor;                                      \n" \
"                                                         \n" \
"in vec2 TexCoord;                                        \n" \
"                                                         \n" \
"uniform sampler2D texture1;                              \n" \
"                                                         \n" \
"void main()                                              \n" \
"{                                                        \n" \
"    FragColor = texture(texture1, TexCoord);             \n" \
"}                                                        \n" \
        

We'll also create a 3d cube, and a checkerboard texture:


float vertices3d[] = {
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

unsigned int VBO3D, VAO3D;
glGenVertexArrays(1, &VAO3D);
glGenBuffers(1, &VBO3D);
glBindVertexArray(VAO3D);
glBindBuffer(GL_ARRAY_BUFFER, VBO3D);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices3d), vertices3d, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture coord attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

unsigned int checkerboard[] = {
    0xFFFFFFFF, 0xFF000000,
    0xFF000000, 0xFFFFFFFF
};

GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, checkerboard);
glGenerateMipmap(GL_TEXTURE_2D);
        

Since we'll be rendering both 3D and 2D, we'll need to make sure GL_DEPTH_TEST is only enabled for the 3D part of our rendering:


glDisable(GL_DEPTH_TEST);
glUseProgram(program);
glBindVertexArray(VAO);

float projection[16];
vd_fw_u_ortho(0.f, (float)w, (float)h, 0.f, -1.f, 1.f, projection);
glUniformMatrix4fv(glGetUniformLocation(program, "projection"), 1, GL_FALSE, projection);
// ... Rest of 2D rendering code

glEnable(GL_DEPTH_TEST);
// 3D rendering code
        

Before our main loop, we're also going to add some variables for the camera:


float camera_position[3] = {0.f, 0.f, -2.f};
float camera_yaw   = 0.f;
float camera_pitch = 30.f;
float deg2rad = 3.14159265359f / 180.f;
float camera_speed = 2.f;
        

Now for rendering the cube depending on camera position & rotation:


glEnable(GL_DEPTH_TEST);
float fw = (float)w;
float fh = (float)h;

// Compute forward vector
float camera_forward[3] = {
    VD_FW_COS(deg2rad * camera_pitch) * VD_FW_SIN(deg2rad * camera_yaw),
    VD_FW_SIN(deg2rad * camera_pitch),
    VD_FW_COS(deg2rad * camera_pitch) * VD_FW_COS(deg2rad * camera_yaw)
};

// Normalize forward vector
float camera_forward_len = VD_FW_SQRT(
    camera_forward[0] * camera_forward[0] +
    camera_forward[1] * camera_forward[1] +
    camera_forward[2] * camera_forward[2]);

camera_forward[0] = camera_forward[0] / camera_forward_len;
camera_forward[1] = camera_forward[1] / camera_forward_len;
camera_forward[2] = camera_forward[2] / camera_forward_len;

float camera_ref_up[3] = {0.f, 1.f, 0.f};

// Compute right vector by taking the cross product of foward x up
float camera_right[3] = {
    camera_forward[1] * camera_ref_up[2] - camera_forward[2] * camera_ref_up[1],
    camera_forward[2] * camera_ref_up[0] - camera_forward[0] * camera_ref_up[2],
    camera_forward[0] * camera_ref_up[1] - camera_forward[1] * camera_ref_up[0],
};

// Normalize right vector
float camera_right_len = VD_FW_SQRT(
    camera_right[0] * camera_right[0] +
    camera_right[1] * camera_right[1] +
    camera_right[2] * camera_right[2]);

camera_right[0] = camera_right[0] / camera_right_len;
camera_right[1] = camera_right[1] / camera_right_len;
camera_right[2] = camera_right[2] / camera_right_len;

glViewport(0, 0, w, h - 30);
glUseProgram(program3d);
glBindVertexArray(VAO3D);

float projection[16] = {0.f};
vd_fw_u_perspective(60.f, fw / fh, 0.1f, 100.0f, projection);

float view[16] = {0.f};
float ctar[3] = {
    camera_position[0] + camera_forward[0],
    camera_position[1] + camera_forward[1],
    camera_position[2] + camera_forward[2]};
float cup[3]  = {0.f, 1.f, 0.f};
vd_fw_u_lookat(camera_position, ctar, cup, view);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

glUseProgram(program3d);
glUniformMatrix4fv(glGetUniformLocation(program3d, "projection"), 1, GL_FALSE, projection);
glUniformMatrix4fv(glGetUniformLocation(program3d, "view"), 1, GL_FALSE, view);
glUniform1i(glGetUniformLocation(program3d, "texture1"), 0);

glBindVertexArray(VAO3D);

glDrawArrays(GL_TRIANGLES, 0, 36);
        

And here we have a 3d cube! As you can see, I've already added the movement of the camera for demonstration purposes, but we'll have a look at input in the next section.


Handle Input

We've already seen some mouse state handling, but let's look at the main functions used for input:

  • vd_fw_delta_s, to get delta time since last frame
  • vd_fw_get_mouse_state, to read the mouse state
  • vd_fw_get_mouse_delta, to read how much the mouse moved since last frame
  • vd_fw_get_key_down, to check if a key is currently down
  • vd_fw_get_key_pressed, to check if a key was just pressed
  • vd_fw_set_mouse_locked, to hide and confine the mouse to the window

So let's add some camera manipulation, we'll start by getting the delta time in seconds:


float ds = vd_fw_delta_s();
        

It's also common to lock the mouse and hide it, so let's do that as well at the start of the application:


vd_fw_set_mouse_locked(1);
        

Here's also the snippet moving the camera:


// Get key states
float fwdir = (float)(vd_fw_get_key_down('W') - vd_fw_get_key_down('S'));
float rgdir = (float)(vd_fw_get_key_down('A') - vd_fw_get_key_down('D'));
float updir = (float)(vd_fw_get_key_down('Q') - vd_fw_get_key_down('E'));

// Compute overall move direction
float camera_move_dir[3] = {
    fwdir * camera_forward[0] + rgdir * camera_right[0] + updir * camera_ref_up[0],
    fwdir * camera_forward[1] + rgdir * camera_right[1] + updir * camera_ref_up[1],
    fwdir * camera_forward[2] + rgdir * camera_right[2] + updir * camera_ref_up[2],
};

// Normalize move direction, if it's length is not too small (i.e. we're not pressing any keys)
float camera_dir_lensq = 
    camera_move_dir[0] * camera_move_dir[0] +
    camera_move_dir[1] * camera_move_dir[1] +
    camera_move_dir[2] * camera_move_dir[2];

if (camera_dir_lensq > 0.0001f) {
    // Normalize move direction and apply it to the camera's position
    float camera_move_dir_len = VD_FW_SQRT(camera_dir_lensq);

    camera_move_dir[0] = camera_move_dir[0] / camera_move_dir_len;
    camera_move_dir[1] = camera_move_dir[1] / camera_move_dir_len;
    camera_move_dir[2] = camera_move_dir[2] / camera_move_dir_len;

    camera_position[0] += camera_move_dir[0] * camera_speed * ds;
    camera_position[1] += camera_move_dir[1] * camera_speed * ds;
    camera_position[2] += camera_move_dir[2] * camera_speed * ds;
}
        

Finally, inside of our rendering loop, we compute the camera's pitch and yaw based on the mouse delta:


if (vd_fw_get_mouse_locked()) {
    float mouse_delta_x, mouse_delta_y;
    vd_fw_get_mouse_delta(&mouse_delta_x, &mouse_delta_y);

    camera_yaw   += mouse_delta_x;
    camera_pitch -= mouse_delta_y;
};

if (camera_pitch < -89.9f) camera_pitch = -89.9f;
if (camera_pitch > +89.9f) camera_pitch = +89.9f;

if (camera_yaw > +360.f) camera_yaw -= 360.f;
if (camera_yaw < -360.f) camera_yaw += 360.f;

float camera_forward[3] = {
    VD_FW_COS(deg2rad * camera_pitch) * VD_FW_SIN(deg2rad * camera_yaw),
    // ..
        

Okay, we can move the cube, but we can't really exit the application easily, since the mouse is confined. Let's make 'Shift + F1' unlock the mouse:


if (vd_fw_get_key_pressed(VD_FW_KEY_F1) && vd_fw_get_key_down(VD_FW_KEY_LSHIFT)) {
    vd_fw_set_mouse_locked(!vd_fw_get_mouse_locked());
}
        

And we finally have a resizable, draggable window with our own custom button and cube.


Set the Window Icon

To set the window icon, call:


// Image format must be in ARGB little-endian (i.e 0xAARRGGBB)
vd_fw_set_app_icon(pixels, width, height);
        

If you're following along with the tutorial, we will temporarily change the window to have decorations, so that we can see the icon drawn by Windows in the caption:


vd_fw_init(& (VdFwInitInfo) {
    .gl = {
        .version = VD_FW_GL_VERSION_3_3,
        .debug_on = 0,
    },
    .window_options = {
        .borderless = 0, // <---
    }
});
        

Let's draw a simple red-ish gradient from top to bottom, and set that as our icon:


unsigned int icon_pixels[32*32];
for (int y = 0; y < 32; ++y) {
    for (int x = 0; x < 32; ++x) {

        float t = ((float)(y * 32 + x)) / (32.f * 32.f);

        t = (VD_FW_SIN(t * 2) + 1.0f) * 0.5f;

        float r = 0.7f * t;
        float g = 0.2f * t;
        float b = 0.0f * t;

        icon_pixels[y * 32 + x] = 0xFF << 24                       |
                                  ((unsigned char)(r * 255)) << 16 |
                                  ((unsigned char)(g * 255)) <<  8 |
                                  ((unsigned char)(b * 255)) <<  0;
    }
}
vd_fw_set_app_icon(icon_pixels, 32, 32);
        

On Windows, the icon will also be shown in:

  • The App Bar (Taskbar), and
  • The Window switcher (Alt + Tab)


Minimize and Maximize the Window

Let's add 2 more buttons for minimizing and maximizing the window:


{
    float button_color[4] = {0.7f, 0.7f, 0.7f, 1.0f};
    int mouse_inside_maximize_button =
        (mx > ((float)w - 30.f * 2.f)) && (mx < ((float)w - 30.f)) &&
        (my > (0.f) && my < (30.f));

    if (mouse_inside_maximize_button) {
        button_color[0] = 0.9f;
        button_color[1] = 0.9f;
        button_color[2] = 0.9f;
        button_color[3] = 1.0f;
    }

    glUniform4f(glGetUniformLocation(program, "rect_color"), button_color[0], button_color[1], button_color[2], button_color[3]);
    glUniform2f(glGetUniformLocation(program, "rect_size"), 20.f, 20.f);
    glUniform2f(glGetUniformLocation(program, "rect_off"), (float)w - 30.f * 2.f, 5.f);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

{
    float button_color[4] = {0.7f, 0.7f, 0.7f, 1.0f};
    int mouse_inside_minimize_button =
        (mx > ((float)w - 30.f * 3.f)) && (mx < ((float)w - 30.f * 2.f)) &&
        (my > (0.f) && my < (30.f));

    if (mouse_inside_minimize_button) {
        button_color[0] = 0.9f;
        button_color[1] = 0.9f;
        button_color[2] = 0.9f;
        button_color[3] = 1.0f;
    }

    glUniform4f(glGetUniformLocation(program, "rect_color"), button_color[0], button_color[1], button_color[2], button_color[3]);
    glUniform2f(glGetUniformLocation(program, "rect_size"), 20.f, 5.f);
    glUniform2f(glGetUniformLocation(program, "rect_off"), (float)w - 30.f * 3.f, 20.f);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
        

And to implement the buttons:


if (mouse_inside_maximize_button && vd_fw_get_mouse_clicked(VD_FW_MOUSE_BUTTON_LEFT)) {
    int is_maximized;
    vd_fw_get_maximized(&is_maximized);
    if (is_maximized) {
        vd_fw_normalize();
    } else {
        vd_fw_maximize();
    }
}

if (mouse_inside_minimize_button && vd_fw_get_mouse_clicked(VD_FW_MOUSE_BUTTON_LEFT)) {
    vd_fw_minimize();
}
        

Finally, we have a window that can be maximized and minized with customizable buttons.


Use the Discrete GPU

The OpenGL specification makes no distinction between the presence of multiple GPUs inside the target system; one will be automatically selected based on vendor preference settings host OS, and other factors that the developer will not be able to affect.

However, for most systems there is a way to tell the OS at least what kind of GPU the application prefers. This library can be instructed to do that through using:


#define VD_FW_PREFER_DISCRETE_GPU    // Prefer D-GPU
// Or
#define VD_FW_PREFER_INTEGRATED_GPU  // Prefer I-GPU (The one inside your CPU)
        

Of course, this doesn't mean that your target application will only use the GPU type you specified, but it will signal to whatever system decides this that the application prefers that type of GPU.

There are many more technicalities, the most important of which to know is: The OpenGL backend will change/swap GPUs if run on an Optimus, or other kind of laptop that features GPU switching. It will also change (depending on the operating system, version, etc.), if the window enters a full-screen state.

Most of these 2 GPU devices are high end gaming laptops, which use the integrated GPU for low priority tasks, such as the web browser, and keep the discrete GPU turned off until a demanding task, like a game needs it.

My suggestion is:

  • For GUI applications:
    Don't set any GPU preference. These apps are supposed to use few system resources anyway. This will guarantee that resizing, going fullscreen, etc.. will match the display context to the currently used one by other applications.
  • For games:
    Always use the discrete GPU, since those are the ones that implement the most OpenGL extensions, as well as the fastest ones. But be aware that initial startup time (especially on gaming laptops with 2 GPUs), will be affected.


Reference


Input Mappings

This is a complete list of all possible input mappings supported by this library.

Alternatively, click below to open an interactive interface:


Here's all the mapped keybindings. For most of them, considerations were taken to map the values to ASCII symbols that correspond to the physical key on a US ANSI/ISO keyboard layout.


enum {
    VD_FW_KEY_UNKNOWN       = 0,
    VD_FW_KEY_F1            = 1,
    VD_FW_KEY_F2            = 2,
    VD_FW_KEY_F3            = 3,
    VD_FW_KEY_F4            = 4,
    VD_FW_KEY_F5            = 5,
    VD_FW_KEY_F6            = 6,
    VD_FW_KEY_F7            = 7,
    VD_FW_KEY_F8            = 8,
    VD_FW_KEY_F9            = 9,
    VD_FW_KEY_F10           = 10,
    VD_FW_KEY_F11           = 11,
    VD_FW_KEY_F12           = 12,
    VD_FW_KEY_F13           = 13,
    VD_FW_KEY_F14           = 14,
    VD_FW_KEY_F15           = 15,
    VD_FW_KEY_F16           = 16,
    VD_FW_KEY_F17           = 17,
    VD_FW_KEY_F18           = 18,
    VD_FW_KEY_F19           = 19,
    VD_FW_KEY_F20           = 20,
    VD_FW_KEY_F21           = 21,
    VD_FW_KEY_F22           = 22,
    VD_FW_KEY_F23           = 23,
    VD_FW_KEY_F24           = 24,
    VD_FW_KEY_BACKSPACE     = 25,  
    VD_FW_KEY_INS           = 26,
    VD_FW_KEY_HOME          = 27,
    VD_FW_KEY_PGUP          = 28,
    VD_FW_KEY_DEL           = 29,
    VD_FW_KEY_END           = 30,
    VD_FW_KEY_PGDN          = 31,
    VD_FW_KEY_SPACE         = 32,  // ' ' 
    VD_FW_KEY_LCONTROL      = 33,
    VD_FW_KEY_RCONTROL      = 34,
    VD_FW_KEY_LALT          = 35,
    VD_FW_KEY_RALT          = 36,
    VD_FW_KEY_LSHIFT        = 37,
    VD_FW_KEY_RSHIFT        = 38,
    VD_FW_KEY_QUOTE         = 39,  // '\''
    VD_FW_KEY_ARROW_UP      = 40,
    VD_FW_KEY_ARROW_LEFT    = 41,
    VD_FW_KEY_ARROW_DOWN    = 42,
    VD_FW_KEY_ARROW_RIGHT   = 43,
    VD_FW_KEY_COMMA         = 44,  // ','
    VD_FW_KEY_MINUS         = 45,  // '-'
    VD_FW_KEY_DOT           = 46,  // '.'
    VD_FW_KEY_SLASH_FORWARD = 47,  // '/'
    VD_FW_KEY_0             = 48,  // '0'
    VD_FW_KEY_1             = 49,  // '1'
    VD_FW_KEY_2             = 50,  // '2'
    VD_FW_KEY_3             = 51,  // '3'
    VD_FW_KEY_4             = 52,  // '4'
    VD_FW_KEY_5             = 53,  // '5'
    VD_FW_KEY_6             = 54,  // '6'
    VD_FW_KEY_7             = 55,  // '7'
    VD_FW_KEY_8             = 56,  // '8'
    VD_FW_KEY_9             = 57,  // '9'
    VD_FW_KEY_ENTER         = 58,
    VD_FW_KEY_SEMICOLON     = 59,  // ';'
    VD_FW_KEY_TAB           = 60,
    VD_FW_KEY_EQUALS        = 61,  // '='
    VD_FW_KEY_CAPITAL       = 62,
    VD_FW_KEY_ESCAPE        = 63,
    VD_FW_KEY_RESERVED1     = 64,  // '@'
    VD_FW_KEY_A             = 65,  // 'A'
    VD_FW_KEY_B             = 66,  // 'B'
    VD_FW_KEY_C             = 67,  // 'C'
    VD_FW_KEY_D             = 68,  // 'D'
    VD_FW_KEY_E             = 69,  // 'E'
    VD_FW_KEY_F             = 70,  // 'F'
    VD_FW_KEY_G             = 71,  // 'G'
    VD_FW_KEY_H             = 72,  // 'H'
    VD_FW_KEY_I             = 73,  // 'I'
    VD_FW_KEY_J             = 74,  // 'J'
    VD_FW_KEY_K             = 75,  // 'K'
    VD_FW_KEY_L             = 76,  // 'L'
    VD_FW_KEY_M             = 77,  // 'M'
    VD_FW_KEY_N             = 78,  // 'N'
    VD_FW_KEY_O             = 79,  // 'O'
    VD_FW_KEY_P             = 80,  // 'P'
    VD_FW_KEY_Q             = 81,  // 'Q'
    VD_FW_KEY_R             = 82,  // 'R'
    VD_FW_KEY_S             = 83,  // 'S'
    VD_FW_KEY_T             = 84,  // 'T'
    VD_FW_KEY_U             = 85,  // 'U'
    VD_FW_KEY_V             = 86,  // 'V'
    VD_FW_KEY_W             = 87,  // 'W'
    VD_FW_KEY_X             = 88,  // 'X'
    VD_FW_KEY_Y             = 89,  // 'Y'
    VD_FW_KEY_Z             = 90,  // 'Z'
    VD_FW_KEY_BRACKET_OPEN  = 91,  // '['
    VD_FW_KEY_SLASH_BACK    = 92,  // '\\'
    VD_FW_KEY_BRACKET_CLOSE = 93,  // ']'
    VD_FW_KEY_MEDIA_NEXT    = 94,  // Media Next Track
    VD_FW_KEY_MEDIA_PREV    = 95,  // Media Prev Track
    VD_FW_KEY_BACKTICK      = 96,  // '`'
    VD_FW_KEY_MEDIA_PLAY    = 97,  // Media Play/Pause
    VD_FW_KEY_NUMPAD_0      = 98,  // Numpad 0
    VD_FW_KEY_NUMPAD_1      = 99,  // Numpad 1
    VD_FW_KEY_NUMPAD_2      = 100, // Numpad 2
    VD_FW_KEY_NUMPAD_3      = 101, // Numpad 3
    VD_FW_KEY_NUMPAD_4      = 102, // Numpad 4
    VD_FW_KEY_NUMPAD_5      = 103, // Numpad 5
    VD_FW_KEY_NUMPAD_6      = 104, // Numpad 6
    VD_FW_KEY_NUMPAD_7      = 105, // Numpad 7
    VD_FW_KEY_NUMPAD_8      = 106, // Numpad 8
    VD_FW_KEY_NUMPAD_9      = 107, // Numpad 9
    VD_FW_KEY_MAX,
};
        

int vd_fw_init(VdFwInitInfo *info);

Initialize fw. Call this before any other call

info Custom options when initializing. Leave null for default
Returns (Reserved)
int vd_fw_running(void);

Check if the application is running. Call this every frame

Returns 1 if running, 0 if not
int vd_fw_swap_buffers(void);

End rendering and swap buffers. Call this right at the end of your rendering code.

Returns (Reserved)
int vd_fw_get_size(int *w, int *h);

Get the size of the window, in pixels

w The width of the window, in pixels
h The height of the window, in pixels
Returns (Reserved)
void vd_fw_set_size(int w, int h);

Set the window size, in pixels

w The width of the window, in pixels
h The height of the window, in pixels
void vd_fw_set_size_min(int w, int h);

Set minimum window size, in pixels.

w The minimum width of the window, set to 0 to use default.
h The minium height of the window, set to 0 to use default.
void vd_fw_set_size_max(int w, int h);

Set maximum window size, in pixels.

w The maximum width of the window, set to 0 to use default.
h The maxium height of the window, set to 0 to use default.
int vd_fw_get_minimized(int *minimized);

Get if the window is minimized

minimized Whether the window is minimized
Returns 1 if the minimization state of the window was changed
void vd_fw_minimize(void);

Minimize the window

int vd_fw_get_maximized(int *maximized);

Get if the window is maximized

maximized Whether the window is maximized
Returns 1 if the maximization state of the window was changed
void vd_fw_maximize(void);

Maximize the window

void vd_fw_normalize(void);

Restores the window state, if it's minimized or maximized

void vd_fw_set_fullscreen(int on);

Enter/exit fullscreen.

on Whether to enter or exit fullscreen.
int vd_fw_get_fullscreen(void);

Get current fullscreen state.

Returns 1 if in fullscreen, 0 otherwise.
int vd_fw_get_focused(int *focused);

Gets whether the window is focused

focused Pointer to int which will receive the value of focus
Returns 1 if the focus has changed. There's no point in checking the value of focused otherwise.
void vd_fw_set_ncrects(int caption[4], int count, int (*rects)[4]);

Set the draggable area of the window, any sub-rectangles to ignore

caption The whole draggable area
count The count of sub-rectangles that will be excluded from dragging
rects An array of count rectangles to exclude
void vd_fw_set_receive_ncmouse(int on);

Set to receive mouse events outside of the non-client area

on if you want to receive those events, 0 otherwise (default)
float vd_fw_get_scale(void);

Gets the backing scale factor

Returns The backing scale factor (1.0f: 1:1 scale, 2.0f, 2:1 scale, etc...)
void vd_fw_set_title(const char *title);

Set the title of the window

title The new title of the window
void vd_fw_set_app_icon(void *pixels, int width, int height);

Set the icon of the window and application

pixels A packed A8B8G8R8 pixel buffer
width The width of the icon, in pixels, must be at least 16px
height The height of the icon, in pixels, must be at least 16px
unsigned long long vd_fw_delta_ns(void);

Get the time (in nanoseconds) since the last call to vd_fw_swap_buffers

Returns The delta time (in nanoseconds)
float vd_fw_delta_s(void);

Get the time (in seconds) since the last call to vd_fw_swap_buffers

Returns The delta time (in seconds)
int vd_fw_set_vsync_on(int on);

Set the number of frames to sync on

on Use: 0 for no sync, 1 for sync every frame and 2 for sync every other frame
Returns 1 if the change was applied successfully
int vd_fw_get_mouse_state(int *x, int *y);

Read the mouse state.

x The horizontal position of the mouse, in pixels (left -> right)
y The vertical position of the mouse, in pixels (top -> bottom)
Returns The mouse button state
int vd_fw_get_mouse_statef(float *x, float *y);

Read the mouse state (float version).

x The horizontal position of the mouse, in pixels (left -> right)
y The vertical position of the mouse, in pixels (top -> bottom)
Returns The mouse button state
int vd_fw_get_mouse_clicked(int button);

Get if the supplied button was just clicked

button The button to check
Returns Whether the button was clicked last frame
int vd_fw_get_mouse_released(int button);

Get if the supplied button was just released

button The button check
Returns Whether the button was released last frame
void vd_fw_set_mouse_capture(int on);

Capture mouse and receive events outside of the window region.

on Whether to set this behavior on (default: off).
void vd_fw_get_mouse_delta(float *dx, float *dy);

Get the mouse movement in raw smoothed pixels. Use this over computing delta yourself.

dx The horizontal delta movement of the mouse
dy The vertical delta movement of the mouse
void vd_fw_set_mouse_locked(int locked);

Lock/Unlock the mouse to the center of the window, hiding its cursor

locked Whether to lock or unlock the mouse (default: unlocked)
int vd_fw_get_mouse_locked(void);

Gets whether the mouse is locked (by vd_fw_set_mouse_locked).

Returns Whether the mouse is locked (1 or 0)
int vd_fw_get_mouse_wheel(float *dx, float *dy);

Read the mouse wheel state.

dx The delta of horizontal wheel (either trackpad swipe right, or ctrl + mousewheel)
dy The delta of vertical wheel
Returns 1 if the wheel moved, 0 if not
int vd_fw_get_key_pressed(int key);

Get whether a key was just pressed this frame

key The key to check
Returns Whether this key was pressed this frame
int vd_fw_get_key_down(int key);

Get the last known state of this key

key The key to check
Returns Whether this key is down currently
const char* vd_fw_get_key_name(VdFwKey k);

Convert key to string.

k The key
Returns The key's name
void* vd_fw_get_internal_window_handle(void);

Returns a pointer to the window handle allocated by the library

Returns Win32(HWND*), MacOS(NSWindow*), X11(Window*)
unsigned int vd_fw_compile_shader(unsigned int type, const char *source);

Compile a GLSL shader and check for errors

type The shader type
source The shader source code
Returns The shader handle
int vd_fw_compile_or_hotload_program(unsigned int *program, unsigned long long *last_compile, const char *vertex_file_path, const char *fragment_file_path);

Compiles a program, if any of the shader sources have been modified. You should call this every frame

program Pointer to program (GLuint), initialize it to zero before rendering loop
last_compile Pointer to last compilation time, initialize it to zero before rendering loop, and don't use it in any other way
vertex_file_path The relative (or absolute) path to the vertex shader source
fragment_file_path The relative (or absolute) path to the fragment shader source
Returns 1 if successful, 0 if encountered any breaking error. You don't really need to check this.
void vd_fw_u_ortho(float left, float right, float bottom, float top, float near, float far, float out[16]);

Construct an orthographic projection matrix

left The left side
right The right side
top The top side
bottom The bottom side
near The near plane
far The far plane
out The output matrix
void vd_fw_u_perspective(float fov, float aspect, float near, float far, float out[16]);

Construct a perspective projection matrix

fov The vertical fov, in degrees
aspect The aspect ratio
far The far plane
near The near plane
out The output matrix
void vd_fw_u_lookat(float eye[3], float target[3], float updir[3], float out[16]);

Construct a view matrix

eye Position of the camera
target Look target position
updir The up direction
out The output matrix
© Michael Dodis, 2025-2026. This page was created with DOCUSPEC