Brief


A header-only library that gets you a semi-customizable window and (optionally) 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/gamepad
  • 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)
  • Or let you use your Graphics API of choice
  • Set the window icon
  • All of that, with as minimal dependencies as possible

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)
  • Provides 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

        if (vd_fw_close_requested()) {
            vd_fw_quit();
        }

        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 inline function signature.
#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_LOG Use to define the macro used by the library to log errors or other information.
#define VD_FW_ENDIANNESS Defines the byte order of the platform. Suggested to leave as is.
#define VD_FW_CUSTOM_TYPEDEFS Use to override the primitive typedefs used by the library. The defaults are:

#define VdFwU8   uint8_t
#define VdFwU16  uint16_t
#define VdFwU32  uint32_t
#define VdFwI32  int32_t
#define VdFwSz   size_t
#define VdFwU64  uint64_t
                    
#define VD_FW_GAMEPAD_COUNT_MAX 16 The maximum number of simulataneously connected gamepads that the library will support. Defaults to 16.
#define VD_FW_GAMEPAD_DB_DEFAULT Use to include (or not) the default gamepad DB.
#define VD_FW_GAMEPAD_DB_DEFAULT_EXTERNAL Use with VD_FW_GAMEPAD_DB_DEFAULT to instruct the library to include a C file generated by gamecontrollerdb.c.
#define VD_FW_NCRECTS_MAX 16 The maximum number of non-client rectangles that will be supported by the library. Defaults to 16.
#define VD_FW_CODEPOINT_BUFFER_COUNT 8 The size (in characters) of the character input history buffer. Defaults to 8.
#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.dll
  2. OpenGL32.dll
  3. Dwmapi.dll
  4. Gdi32.dll
  5. uxtheme.dll
  6. shell32.dll
  7. winmm.dll
  8. SetupAPI.dll
  9. Advapi32.dll
  10. xinput1_4.dll
  11. xinput1_3.dll
  12. xinput9_1_0.dll

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
Linux (DRM) Planned
iOS Planned
Android Planned
Web Planned
FreeBSD Not Planned
OpenBSD Not Planned
NetBSD Not Planned
Haiku Not Planned
MacOS (Legacy/x86_64) Not Planned
tvOS Not Planned
visionOS Not Planned
PlayStation Consoles NDA
Nintendo Consoles NDA

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
  • Ofek
    For the display monitor enumeration code sample

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 1                                       // 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 the window is closed

        if (vd_fw_close_requested()) {                       // Close the window if the
            vd_fw_quit();                                    // user requested it
        }

        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.



Note that resizing or moving the window will never pause the render loop, with OpenGL or any other API.


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, which 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 should 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.


Add Gamepad Support

This library can provide Gamepad input as well as basic rumble for common (lo/hi) motors. In this section, we'll add some basic code to our existing cube program to move the camera using the gamepad.

Starting from the part where we were getting the Forward and Right input directions:


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'));
        

Let's modify the code to also get input from the gamepad:


float fwdir = 0.f;
float rgdir = 0.f;
float updir = 0.f;

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

if (vd_fw_get_gamepad_count() > 0) {

    float left_stick[2];
    vd_fw_get_gamepad_axis(0, VD_FW_GAMEPAD_LV, &left_stick[0]);
    vd_fw_get_gamepad_axis(0, VD_FW_GAMEPAD_LH, &left_stick[1]);

    fwdir -= left_stick[0];
    rgdir -= left_stick[1];
}
        

And now let's continue with using the right stick to manipulate the look direction:


// Here we ignore gamepad input if mouse is locked
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;
} else if (vd_fw_get_gamepad_count() > 0) {
    float right_stick[2];
    vd_fw_get_gamepad_axis(0, VD_FW_GAMEPAD_RH, &right_stick[0]);
    vd_fw_get_gamepad_axis(0, VD_FW_GAMEPAD_RV, &right_stick[1]);
    camera_yaw   += right_stick[0] * ds * 100.f;
    camera_pitch -= right_stick[1] * ds * 100.f;
}
        

We could go on with mapping up/down to triggers, but I think you get the idea.

Of course, some controllers with either stick drift or very sensitive sticks may result in our camera moving even if we're not touching the controller. This is handled by implementing deadzones. This library will not do that for you, as radial input mapping can be very dependent on the use case of the application. But let's go through a simple linear deadzone mapping.

We will create a function that will map raw stick input to the appropriate value using a deadzone:


static void map_stick_deadzone(float input[2], float deadzone, float output[2])
{
    output[0] = output[1] = 0.f;

    float magnitude = VD_FW_SQRT(input[0] * input[0] + input[1] * input[1]);
    if ((magnitude <= 0.000001f) || (magnitude < deadzone)) {
        return;    
    }

    float norm_x = input[0] / magnitude;
    float norm_y = input[1] / magnitude;

    float norm_magnitude = (magnitude - deadzone) / (1.f - deadzone);
    if (norm_magnitude > 1.f) norm_magnitude = 1.f;

    output[0] = norm_x * norm_magnitude;
    output[1] = norm_y * norm_magnitude;
}
        

Given a 'deadzone' parameter between 0 and 1, we map the input from [deadzone, 1] to our space, and ignore anything less that the deadzone.

Let's change the code in our input handling. In our processing of the right stick (camera rotation):


float right_stick_raw[2];
float right_stick[2];
vd_fw_get_gamepad_axis(0, VD_FW_GAMEPAD_RH, &right_stick_raw[0]);
vd_fw_get_gamepad_axis(0, VD_FW_GAMEPAD_RV, &right_stick_raw[1]);

map_stick_deadzone(right_stick_raw, DEADZONE_STICK, right_stick);

camera_yaw   += right_stick[0] * ds * 100.f;
camera_pitch -= right_stick[1] * ds * 100.f;
        

And we follow the same pattern for the left stick for camera movement:


float left_stick_raw[2];
float left_stick[2];
vd_fw_get_gamepad_axis(0, VD_FW_GAMEPAD_LV, &left_stick_raw[0]);
vd_fw_get_gamepad_axis(0, VD_FW_GAMEPAD_LH, &left_stick_raw[1]);

map_stick_deadzone(left_stick_raw, DEADZONE_STICK, left_stick);

fwdir -= left_stick[0];
rgdir -= left_stick[1];
        

Switching Graphics APIs

Apart from OpenGL, this library allows you to use other Graphics APIs. This is done by specifying the graphics API VdFwInitInfo::api.

Options:

  • OpenGL (default): VD_FW_GRAPHICS_API_OPENGL
  • Custom: VD_FW_GRAPHICS_API_CUSTOM
  • Software Rendering (in progress): VD_FW_GRAPHICS_API_PIXEL_BUFFER

Additionally, you can switch graphics APIs at runtime using vd_fw_set_graphics_api:


vd_fw_set_graphics_api(VD_FW_GRAPHICS_API_CUSTOM, NULL);
        

A sample is provided here. Currently it switches between OpenGL and DirectX11 using the spacebar. Each graphics API will then draw a simple texture at the center of the screen to indicate which is the current API it's using:

Following is a basic overview of the code. We start with an interface struct that has function pointers that each graphics backend must support. In a real project, this would either be your RHI definitions, or the functions needed to render your scene.


typedef struct {
    GraphicsBackend backend;
    void (*init)(void *internal_window_handle);
    void (*kill)(void);
    void (*resize)(int w, int h);
    void (*draw_with_background_color)(float r, float g, float b, float a);
} GraphicsBackendImpl;

static VdFwGraphicsApi cv_graphics_backend_to_fw_graphics_api(GraphicsBackend backend)
{
    switch (backend) {
        case GRAPHICS_BACKEND_OPENGL: return VD_FW_GRAPHICS_API_OPENGL;
        default: return VD_FW_GRAPHICS_API_CUSTOM;
    }
}

Next, each implementation is declared, and an array of graphics backends is written depending on what the build target supports:


extern GraphicsBackendImpl OpenGL_Impl;
#ifdef _WIN32
extern GraphicsBackendImpl D3D11_Impl;
#endif

static GraphicsBackendImpl* Graphics_Backends[] = {
    &OpenGL_Impl,
#ifdef _WIN32
    &D3D11_Impl,
#endif
};
        

Afterwards, we make a function that will destroy the current API's context objects, and initialize the new API:


static void switch_to_graphics_backend(GraphicsBackendImpl *impl)
{
    if (Current_Graphics != NULL) {
        printf("Switching from %s to %s\n",
               graphics_backend_to_string(Current_Graphics->backend),
               graphics_backend_to_string(impl->backend));

        Current_Graphics->kill();
    } else {
        printf("Starting with %s\n",
               graphics_backend_to_string(impl->backend));
    }

    Current_Graphics = impl;
    VdFwOpenGLOptions options;
    options.version = VD_FW_GL_VERSION_4_5;
    options.debug_on = 1;
    vd_fw_set_graphics_api(cv_graphics_backend_to_fw_graphics_api(impl->backend), &options);

    impl->init(vd_fw_get_internal_window_handle());
}
        

We can set VdFwInitInfo::api to VD_FW_GRAPHICS_API_INVALID so that no initialization is attempted during vd_fw_init, and right after call our function to switch the backend so as to have everything following the same codepath:


VdFwInitInfo init_info = {};
init_info.api = VD_FW_GRAPHICS_API_INVALID;
vd_fw_init(&init_info);

switch_to_graphics_backend(Graphics_Backends[current_backend_index]);
        

In the game loop, we check for the event that will cause the graphics API to change, and after vd_fw_swap_buffers, we switch our graphics context:


int will_switch_to_next_backend = 0;
if (vd_fw_get_key_pressed(VD_FW_KEY_SPACE)) {
    will_switch_to_next_backend = 1;
}

vd_fw_swap_buffers(); // <--- IMPORTANT: vd_fw_set_graphics_api must not be called between vd_fw_running and vd_fw_swap_buffers.

if (will_switch_to_next_backend) {
    current_backend_index += 1;
    if (current_backend_index >= count_backends) {
        current_backend_index = 0;
    }

    switch_to_graphics_backend(Graphics_Backends[current_backend_index]);
}
        

The rest of the code is typical graphics programming stuff (setting up buffers, textures rendering context).


Technical Details


Here I will attempt to give you a general idea about some high-impact topics regarding this library and how some functionality is implemented.

In general, the process that was followed when developing the API was to pose the question:

What is the intended/typical functionality a developer may want out of this part of a windowing/input library?

This already helps with filtering API designs based on actual real-world utility, and conformance to convetions defined by older software.

This means, for example, that unlike a lot of other libraries you don't get to figure out which OpenGL loader you need/(should use), and you just want to write a program targeting some OpenGL version, or some other graphics API. Targeting OpenGL for windowing will make sense to someone familiar with its implementation on various platforms, but the gist of it is: It's a highly platform dependent library and heavily tied to the windowing system of that platform.

Another example would be the absence of an event API. Event APIs are one of the most granular forms of generalizing a variable input matrix over non-contiguous time. But even those APIs have their limits; and that mostly has to do with the format and intended usage of the data presented to the user.

A good case for this, is handling touch gestures in a way that's consistent with the platform's vendor's Human Interface Guidelines. Typically, event based APIs will expose some kind of high-granularity touch point down/up event and some lower-granularity gestures like swipe, pinch and tap. But from what I've seen, few if any libraries actually follow the convention set by the vendor.

This of course is not entirely the author's fault; newer input APIs are generally harder to use and little to no documentation is provided by the platform vendor. But, this does not waive an input library's obligation to provide the best possible solution for a problem like viewport manipuation (scrolling, zooming, etc...).

For the sake of brevity, most libraries on Win32 will expose some events that are driven by the Message Queue related to touch, scroll and more. But I've seen no general adoption of APIs like DirectManipulation, even if bigger software packages like browsers actually support that API.

I will wager that besides the sparse documentation, the API of DirectManipulation is so different compared to an event API that its modelling on the format provided by those libraries is either an undesireable venture, or out of the defined scope of that library. As such, we're left with so many useful applications that essentially map touchpad scrolling to a virtual mousewheel and try to animate/smooth the provided scroll wheel events sent by windows to emulate this behavior.

While impressive, it will only take the user 1 second to realize that this is a non-conformant application, despite all of the effort in animating input that is essentially wrong in terms of intended usage.

Of course, as can be seen, DirectManipulation is not used in this library, but I do plan to support it as soon as possible; but a useful API to a touch interface must be formed first.

Another question that determines the API is:

Given a typical application/game on this target platform, what is the expected behavior given some user input?

For this library, one can generalize software into two domains:

  • A utlity or desktop application (likely with some GUI interface), or
  • A game

For applications, we can further divide expected behaviors according to the platform, but a good example is that this library will try to set the theme of your window in Win32 to the appropriate one used by the platform (Mica on Windows 11, Dark titlebar on Windows 10, and so on...).

This library tries as much as possible to provide an interface for creating applications or games that use custom window decorations (or window-chrome in Win32 nomenclature) while not blocking during resize or move operations.

Gamepads

Doing low-level gamepad input is not as easy as one would think (and maybe I should write a separate article detailing my investigation into which APIs this library should use and how), but I will briefly mention the APIs used on a per platform basis:

Platform APIs
Windows RAWINPUT & XInput

For each platform, an index of buttons, axes and pov hats is computed based on the HID parser library used by that platform. This is not guaranteed to be consistent between platforms, and as such the same mappings for every HID must be specified for all target platforms.

As a model of the controller, the XBOX Controller is used to assign a symbolic meaning to button indicies in a way that is intuitive for the developer. Additionally, several buttons aside from the XBOX ones are defined: auxiliary buttons (AUX0, AUX1, ...) and paddle buttons (LEFT_PADDLE0, RIGHT_PADDLE0, ...).

Controller mappings are defined via a gamepad database named RGCDB (with the appropriate file extension). An example of a gamepad mapping entry is provided below:


0300d0424c050000cc09000000017200,Sony DualShock 4,a:b1,b:b2,x:b0,y:b3,leftx:a2,lefty:a3,rightx:a4,righty:a7,lefttrigger:a5,righttrigger:a6,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,leftstick:b10,rightstick:b11,rumble:w05ff0000llhh,face:playstation,class:xbox,platform:Windows,
        

Compared to gamecontrollerdb.txt, the following additions were made:

  • class: The classification of a controller; used to determine its capabilities
  • face: The face style of a controller; the symbols for inputs present on the actual hardware
  • rumble: Several directives that instruct this library on how to send rumble input

The classification of the controller is an enumeration that tries to tell the developer what kind of inputs the controller is capable of (or at least reports). They are listed below:


enum {
    VD_FW_GAMEPAD_CLASS_INVALID = 0,
    // class:nes          | 1 PoV, 2 Control, 2 System
    VD_FW_GAMEPAD_CLASS_NES,
    // class:megadrive    | 1 PoV, 3 Control, 1 System
    VD_FW_GAMEPAD_CLASS_MEGADRIVE,
    // class:genesis      | 1 PoV, 6 Control, 2 System
    VD_FW_GAMEPAD_CLASS_GENESIS,
    // class:snes         | 1 PoV, 4 Control, 2 System, 2 Symmetrical
    VD_FW_GAMEPAD_CLASS_SNES,
    // class:ps1          | 1 PoV, 4 Control, 2 System, 4 Symmetrical
    VD_FW_GAMEPAD_CLASS_PS1,
    // class:joycon       |        4 Control, 2 System, 2 Symmetrical, 1 Clickable Stick
    VD_FW_GAMEPAD_CLASS_JOYCON,
    // class:n64          | 1 PoV, 6 Control, 2 System, 2 Symmetrical, 1 Stick
    VD_FW_GAMEPAD_CLASS_N64,
    // class:ps2          | 1 PoV, 4 Control, 2 System, 4 Symmetrical, 2 Clickable Sticks
    VD_FW_GAMEPAD_CLASS_PS2,
    // class:xbox         | 1 PoV, 4 Control, 2 System, 2 Symmetrical, 2 Clickable Sticks, 2 Symmetrical Axes
    VD_FW_GAMEPAD_CLASS_XBOX,
    // class:ps4          | 1 PoV, 4 Control, 2 System, 2 Symmetrical, 2 Clickable Sticks, 2 Symmetrical Axes, 1 Touchpad
    VD_FW_GAMEPAD_CLASS_PS4,
    // class:steamdeck    | 1 PoV, 4 Control, 2 System, 6 Symmetrical, 2 Clickable Sticks, 2 Symmetrical Axes, 2 Touchpads 
    VD_FW_GAMEPAD_CLASS_STEAMDECK,
    VD_FW_GAMEPAD_CLASS_MAX,
};
typedef VdFwU8 VdFwGamepadClass;
        

The available gamepad face values indicate the styling of the buttons and axes indicated on the controller hardware. Here they are:


enum {
    VD_FW_GAMEPAD_FACE_UNKNOWN = 0,
    VD_FW_GAMEPAD_FACE_NUMBERED,    /* face:numbered */
    VD_FW_GAMEPAD_FACE_XBOX,        /* face:xbox */
    VD_FW_GAMEPAD_FACE_PLAYSTATION, /* face:playstation */
    VD_FW_GAMEPAD_FACE_NINTENDO,    /* face:nintendo */
    VD_FW_GAMEPAD_FACE_MAX,
};
        

On Windows, due to legacy reasons HIDs will report triggers as a single axis value and thus they cannot be both pressed at the same time, by just using raw input. To circumvent this, this library (as well as SDL and many other libraries) correlate mapped gamepad inputs with the XInput controller states to determine which "dwUserIndex" was assigned to the controller by XInput. Once that is done, the trigger values are then retrieved through that API for the remainder of the application's lifetime.

The game controller db mappings can be loaded from a text string via:


vd_fw_add_gamepad_rgcdb(const char *text, int text_len);
        

or:


vd_fw_add_gamepad_db_entry(VdFwGamepadDBEntry *entry);
        

Deadzones are not handled regarding axial inputs. This will be a separate set of functions that will filter gamepad input data, because deadzones can have different implementations based on the game's requirements.

For debugging, a sample utility program was written to test controller inputs. You can find it here. Additionally, for Windows, an HID dump utility exists here.


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,
};
        

For the mouse, the following bit masks may be used:


enum {
    VD_FW_MOUSE_STATE_LEFT_BUTTON_DOWN   = 1 << 0,
    VD_FW_MOUSE_STATE_RIGHT_BUTTON_DOWN  = 1 << 1,
    VD_FW_MOUSE_STATE_MIDDLE_BUTTON_DOWN = 1 << 2,
    VD_FW_MOUSE_STATE_M1_BUTTON_DOWN     = 1 << 3,
    VD_FW_MOUSE_STATE_M2_BUTTON_DOWN     = 1 << 4,

    VD_FW_MOUSE_BUTTON_LEFT   = VD_FW_MOUSE_STATE_LEFT_BUTTON_DOWN,
    VD_FW_MOUSE_BUTTON_RIGHT  = VD_FW_MOUSE_STATE_RIGHT_BUTTON_DOWN,
    VD_FW_MOUSE_BUTTON_MIDDLE = VD_FW_MOUSE_STATE_MIDDLE_BUTTON_DOWN,
    VD_FW_MOUSE_BUTTON_M1     = VD_FW_MOUSE_STATE_M1_BUTTON_DOWN,
    VD_FW_MOUSE_BUTTON_M2     = VD_FW_MOUSE_STATE_M2_BUTTON_DOWN,
};
        

This library supports at most 32 gamepad buttons and 6 axes (as of writing), as well as a game controller mapping database akin to gamecontrollerdb.txt. Regarding HIDs, it supports any amount of buttons, axes and PoV hats, but only 64 buttons, 6 axes and 1 PoV hat can be mapped right now. This is subject to change.

The following buttons can be mapped:


enum {
    // XBox Style Buttons
    VD_FW_GAMEPAD_UNKNOWN = 0,
    VD_FW_GAMEPAD_A,
    VD_FW_GAMEPAD_B,
    VD_FW_GAMEPAD_X,
    VD_FW_GAMEPAD_Y,
    VD_FW_GAMEPAD_DUP,
    VD_FW_GAMEPAD_DDOWN,
    VD_FW_GAMEPAD_DLEFT,
    VD_FW_GAMEPAD_DRIGHT,
    VD_FW_GAMEPAD_START,
    VD_FW_GAMEPAD_BACK,
    VD_FW_GAMEPAD_LEFT_SHOULDER,
    VD_FW_GAMEPAD_RIGHT_SHOULDER,
    VD_FW_GAMEPAD_LEFT_STICK,
    VD_FW_GAMEPAD_RIGHT_STICK,
    VD_FW_GAMEPAD_LEFT_PAD0,
    VD_FW_GAMEPAD_RIGHT_PAD0,
    VD_FW_GAMEPAD_LEFT_PAD1,
    VD_FW_GAMEPAD_RIGHT_PAD1,
    VD_FW_GAMEPAD_LEFT_PAD2,
    VD_FW_GAMEPAD_RIGHT_PAD2,
    VD_FW_GAMEPAD_AUX0,
    VD_FW_GAMEPAD_AUX1,
    VD_FW_GAMEPAD_AUX2,
    VD_FW_GAMEPAD_AUX3,
    VD_FW_GAMEPAD_AUX4,
    VD_FW_GAMEPAD_AUX5,
    VD_FW_GAMEPAD_AUX6,
    VD_FW_GAMEPAD_AUX7,
    VD_FW_GAMEPAD_AUX8,
    VD_FW_GAMEPAD_AUX9,
    VD_FW_GAMEPAD_BUTTON_MAX,

    // Playstation Style Buttons
    VD_FW_GAMEPAD_CROSS    = VD_FW_GAMEPAD_A,
    VD_FW_GAMEPAD_CIRCLE   = VD_FW_GAMEPAD_B,
    VD_FW_GAMEPAD_SQUARE   = VD_FW_GAMEPAD_X,
    VD_FW_GAMEPAD_TRIANGLE = VD_FW_GAMEPAD_Y,
    VD_FW_GAMEPAD_SELECT   = VD_FW_GAMEPAD_BACK,
    VD_FW_GAMEPAD_L1       = VD_FW_GAMEPAD_LEFT_SHOULDER,
    VD_FW_GAMEPAD_R1       = VD_FW_GAMEPAD_RIGHT_SHOULDER,
    VD_FW_GAMEPAD_L3       = VD_FW_GAMEPAD_LEFT_STICK,
    VD_FW_GAMEPAD_R3       = VD_FW_GAMEPAD_RIGHT_STICK,

    VD_FW_GAMEPAD_H = 0 >> 1,
    VD_FW_GAMEPAD_V = 2 >> 1,
    VD_FW_GAMEPAD_L = 0 << 1,
    VD_FW_GAMEPAD_R = 1 << 1,
    VD_FW_GAMEPAD_LH = VD_FW_GAMEPAD_L | VD_FW_GAMEPAD_H,
    VD_FW_GAMEPAD_LV = VD_FW_GAMEPAD_L | VD_FW_GAMEPAD_V,
    VD_FW_GAMEPAD_RH = VD_FW_GAMEPAD_R | VD_FW_GAMEPAD_H,
    VD_FW_GAMEPAD_RV = VD_FW_GAMEPAD_R | VD_FW_GAMEPAD_V,
    VD_FW_GAMEPAD_L2 = 4,
    VD_FW_GAMEPAD_R2 = 5,
    VD_FW_GAMEPAD_LT = VD_FW_GAMEPAD_L2,
    VD_FW_GAMEPAD_RT = VD_FW_GAMEPAD_R2,
    VD_FW_GAMEPAD_AXIS_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_close_requested(void);

Get if the user requested to close the window

Returns Whether the user tried to close the window this frame
void vd_fw_quit(void);

Close the window and end the rendering loop

VdFwPlatform vd_fw_get_platform(void);

Get the current platform.

Returns The current platform.
void vd_fw_set_graphics_api(VdFwGraphicsApi api, VdFwOpenGLOptions *gl_options);

Switch the current graphics API (must not be called between vd_fw_running and vd_fw_swap_buffers)

api The new API to use
gl_options If api is VD_FW_GRAPHICS_API_OPENGL, the options for OpenGL
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 1 if the size changed this frame, 0 otherwise
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 A8R8G8B8 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
const char* vd_fw_get_executable_dir(int *len);

Get the fully-qualified path to the executable without the last path separator

len Length of the UTF-8 string, in bytes
Returns A callee-allocated string. There's no need to free it
int vd_fw_get_monitor_count(void);

Get the total count of monitors. Monitor 0 is the primary monitor of the display

Returns The count of all the monitors
const char* vd_fw_get_monitor_name(int index);

Get the monitor's friendly name via EDID

index The monitor index
Returns The monitor name
VdFwDisplayMode* vd_fw_get_monitor_display_modes(int index, int *count);

(Warning: slow) Get the available display modes of the monitor

index The monitor index
count The total count of display modes
Returns Sorted display modes
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_down(int button);

Get whether the mouse button is down

button The button
Returns 1 if the button is down, 0 otherwise
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
int vd_fw_get_gamepad_count(void);

Gets the number of gamepads currently connected

Returns The number of currently connected gamepads
VdFwU64 vd_fw_get_gamepad_button_state(int index);

Gets the state of all (digital) buttons on the gamepad

index The gamepad index
Returns The button state bitfield (use (1 << VD_FW_GAMEPAD_A/B/AUX0...) to test)
int vd_fw_get_gamepad_down(int index, int button);

Gets the state for a Gamepad button (digital)

index The gamepad index
button The gamepad button to check
Returns 1 for On, 0 for Off
int vd_fw_get_gamepad_pressed(int index, int button);

Gets whether the Gamepad button was just pressed this frame

index The gamepad index
button The gamepad button to check
Returns 1 if the button was just pressed this frame, 0 otherwise
int vd_fw_get_gamepad_axis(int index, int axis, float *out);

Gets the gamepad's axis value

index The gamepad index
axis The axis to check
out The axis value [-1, 1] for directional axes (VD_FW_GAMEPAD_LH, etc..), [0, 1] for triggers
Returns (Reserved)
void vd_fw_set_gamepad_rumble(int index, float rumble_lo, float rumble_hi);

Set the state of a gamepad's force-feedback motors

index The gamepad index
rumble_lo The value of the small/left motor [0, 1]
rumble_hi The value of the big/right motor [0, 1]
VdFwGuid vd_fw_get_gamepad_guid(int index);

Get the gamepad's GUID

index The gamepad index
Returns The guid
VdFwGamepadFace vd_fw_get_gamepad_face(int index);

Get the detected gamepad's face type (i.e. the symbols shown on the physical controller)

index The gamepad index
Returns The face type
const char* vd_fw_get_gamepad_face_name(VdFwGamepadFace face);

Convert gamepad face type to string

face The face type
Returns The face type as a string
VdFwGamepadClass vd_fw_get_gamepad_class(int index);

Get the detected gamepad's classification (i.e. a rough ordered value of the gamepad's capabilities)

index The gamepad index
Returns The class type
const char* vd_fw_get_gamepad_class_name(VdFwGamepadClass klass);

Convert gamepad class type to string

klass The class type
Returns The class type as a string
int vd_fw_get_gamepad_rumble_support(int index);

Get whether this gamepad supports rumble

index The gamepad index
Returns 1 if the gamepad supports rumble, 0 otherwise
void vd_fw_add_gamepad_rgcdb(const char *text, int text_len);

Parse and register gamepad entries from a RGCDB file

text The text file
text_len The text file length in bytes
int vd_fw_parse_gamepad_db_entry(const char *s, int s_len, VdFwGamepadDBEntry *out, VdFwPlatform *out_platform, const char **out_begin_name);

Parse an RGCDB entry ascii string, can be called without initializing this library

s The string
s_len The string's length, in bytes
out The db entry info
out_platform The platform for which this entry is valid
out_begin_name The start of the name part of this gamepad (unused in db entries)
Returns 1 for Success, 0 otherwise
int vd_fw_gamepad_map_entry_is_none(VdFwGamepadMapEntry *entry);

Check if a map entry is a terminating entry. Use to iterate over vd_fw_parse_gamepad_db_entry results

entry The entry
Returns 1 if the entry is a terminating entry
int vd_fw_add_gamepad_db_entry(VdFwGamepadDBEntry *entry);

Add an entry to the runtime gamepad db

entry The entry to add
Returns (Reserved)
void vd_fw_set_gamepad_raw_reports(int on);

Turn On/Off raw button/axis/hat reports

on Whether to enable this behavior
VdFwU64 vd_fw_get_gamepad_raw_buttons(int index);

Get the raw state of at most 64 buttons on this gamepad

index The gamepad index
Returns A bitmask of states (LSB -> MSB) --> (b0 -> b63)
float* vd_fw_get_gamepad_raw_axes(int index, int *count_axes);

Get the raw state of all axes on this gamepad (scaled to [0,1])

index The gamepad index
count_axes The number of axes
Returns A callee-allocated float array of axis values
unsigned short vd_fw_get_num_codepoints(void);

Get the number of characters sent by the user

Returns The count of characters. At most VD_FW_CODEPOINT_BUFFER_COUNT
unsigned int vd_fw_get_codepoint(unsigned short index);

Get the i'th character as a UTF-32 codepoint

index The character index
Returns The Unicode codepoint
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