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...
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.
The library does the loading for you.
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: 
 | 
| #define VD_FW_SIN | Can be predefined to your implementation of sinf.
Options: 
 | 
| #define VD_FW_COS | Can be predefined to your implementation of cosf.
Options: 
 | 
| #define VD_FW_TAN | Can be predefined to your implementation of tanf.
Options: 
 | 
| #define VD_FW_SQRT | Can be predefined to your implementation of sqrtf.
Options: 
 | 
| #define VD_FW_MEMCPY | Can be predefined to your implementation of memcpy.
Options: 
 | 
| #define VD_FW_MEMSET | Can be predefined to your implementation of memset.
Options: 
 | 
| #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:
- User32
- OpenGL32
- Dwmapi
- Gdi32
- uxtheme
- shell32
- 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.
- pthread (compiler flag)
- Cocoa
- Metal
- QuartzCore
- CoreGraphics
- IOSurface
- IOKit
- Foundation
- 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.
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
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 countrects
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,
};
        Initialize fw. Call this before any other call
| info | Custom options when initializing. Leave null for default | 
| Returns | (Reserved) | 
Check if the application is running. Call this every frame
| Returns | 1 if running, 0 if not | 
End rendering and swap buffers. Call this right at the end of your rendering code.
| Returns | (Reserved) | 
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) | 
Set the window size, in pixels
| w | The width of the window, in pixels | 
| h | The height of the window, in pixels | 
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. | 
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. | 
Get if the window is minimized
| minimized | Whether the window is minimized | 
| Returns | 1 if the minimization state of the window was changed | 
Minimize the window
Get if the window is maximized
| maximized | Whether the window is maximized | 
| Returns | 1 if the maximization state of the window was changed | 
Maximize the window
Restores the window state, if it's minimized or maximized
Enter/exit fullscreen.
| on | Whether to enter or exit fullscreen. | 
Get current fullscreen state.
| Returns | 1 if in fullscreen, 0 otherwise. | 
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. | 
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 | 
Set to receive mouse events outside of the non-client area
| on | if you want to receive those events, 0 otherwise (default) | 
Gets the backing scale factor
| Returns | The backing scale factor (1.0f: 1:1 scale, 2.0f, 2:1 scale, etc...) | 
Set the title of the window
| title | The new title of the window | 
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 | 
Get the time (in nanoseconds) since the last call to vd_fw_swap_buffers
| Returns | The delta time (in nanoseconds) | 
Get the time (in seconds) since the last call to vd_fw_swap_buffers
| Returns | The delta time (in seconds) | 
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 | 
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 | 
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 | 
Get if the supplied button was just clicked
| button | The button to check | 
| Returns | Whether the button was clicked last frame | 
Get if the supplied button was just released
| button | The button check | 
| Returns | Whether the button was released last frame | 
Capture mouse and receive events outside of the window region.
| on | Whether to set this behavior on (default: off). | 
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 | 
Lock/Unlock the mouse to the center of the window, hiding its cursor
| locked | Whether to lock or unlock the mouse (default: unlocked) | 
Gets whether the mouse is locked (by vd_fw_set_mouse_locked).
| Returns | Whether the mouse is locked (1 or 0) | 
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 | 
Get whether a key was just pressed this frame
| key | The key to check | 
| Returns | Whether this key was pressed this frame | 
Get the last known state of this key
| key | The key to check | 
| Returns | Whether this key is down currently | 
Convert key to string.
| k | The key | 
| Returns | The key's name | 
Returns a pointer to the window handle allocated by the library
| Returns | Win32(HWND*), MacOS(NSWindow*), X11(Window*) | 
Compile a GLSL shader and check for errors
| type | The shader type | 
| source | The shader source code | 
| Returns | The shader handle | 
Link a GL program and check for errors
| program | The program to link | 
| Returns | 1 on success, 0 otherwise | 
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. | 
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 | 
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 | 
Construct a view matrix
| eye | Position of the camera | 
| target | Look target position | 
| updir | The up direction | 
| out | The output matrix | 
 
