floooh/ sokol
View on GitHubminimal cross-platform standalone C headers
minimal cross-platform standalone C headers
See what's new (30-Aug-2026: sokol_gfx.h: the 'write-transient' update)
Live Samples via WASM (source)
Doom Shareware ported to the Sokol headers (source)
Syntonic Dentiforms Redux demo remaster by Aras Pranckevičius (blog post).
Everybody Wants to Crank the World demo by Aras Pranckevičius, PC/web port via sokol (source).
sokol_gp.h a 2D shape drawing library on top of sokol_gfx.h
NanoVG backend a sokol_gfx backend for the amazing NanoVG library.
Dear ImGui starterkit a self-contained starterkit for writing Dear ImGui apps in C.
qoiview a basic viewer for the new QOI image file format
A 'single-file' Pacman clone in C99, also available in Zig
Soluna, a framework to make 2D games in Lua by @cloudwu
Deep Future: ...and a game implemented with Soluna
Solar Storm, a turn-based scifi artillery game built with Odin and Sokol, released on Steam.
Spanking Runners (Samogonki), arcade racing in a bright and unusual world, released on Steam.
MEG-4 a virtual fantasy console emulator in C89, ported to sokol
A Minigolf game (source).
hIghQube A game demo that used sokol rendering extensively
Senos A music app that uses sokol as backend
'Brick Warrior' - a 1990's Breakout clone ported to Sokol :)
LearnOpenGL examples ported to sokol-gfx (may be outdated) (git repo)
Command line tools (shader compiler)
How to build without a build system: useful details for integrating the Sokol headers into your own project with your favourite C/C++ build system
These are automatically updated on changes to the C headers:
WebAssembly is a 'first-class citizen', one important motivation for the Sokol headers is to provide a collection of cross-platform APIs with a minimal footprint on the web platform while still being useful.
The core headers are standalone and can be used independently from each other.
A blog post with more background info: A Tour of sokol_gfx.h
When a first Sokol integration fails to compile or link, the problem is usually one of the following configuration mismatches:
SOKOL_IMPL (or the per-header SOKOL_*_IMPL define) in exactly one
C or C++ translation unit before including the Sokol headers. Other files
should include the headers without the implementation define.SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL,
SOKOL_WGPU or SOKOL_VULKAN. The backend define must match the shader code
and any window/context setup code you use.sokol_gfx.h and sokol_app.h are used together, use the same backend
define for both headers. sokol_app.h creates the matching window and 3D API
context; sokol_glue.h then forwards the platform-specific environment via
sglue_environment() and sglue_swapchain().sokol_app.h or Metal must compile the
implementation file as Objective-C or Objective-C++ (.m or .mm, or the
equivalent compiler flag), and link the frameworks listed in the header docs.-s USE_WEBGL2=1 when using
SOKOL_GLES3. WebGPU builds use SOKOL_WGPU and need the WebGPU port/options
described in the sokol_gfx.h header comments.SOKOL_GLCORE uses GLX by default; define
SOKOL_FORCE_EGL if your build intentionally uses EGL instead.#pragma comment(lib, ...) directives. MinGW/MSYS2 builds may need
explicit linker flags such as -ld3d11 for SOKOL_D3D11.sokol-shdc version compatible with the Sokol headers and were generated for
the same backend(s) selected at compile time.For exact per-header compiler and linker requirements, see the comment block at
the top of each Sokol header and the build notes in
sokol-samples.
A minimal cross-platform application-wrapper library:
The vanilla Hello-Triangle using sokol_gfx.h, sokol_app.h and the sokol-shdc shader compiler (shader code not shown):
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_log.h"
#include "sokol_glue.h"
#include "triangle-sapp.glsl.h"
static struct {
sg_pipeline pip;
sg_bindings bind;
sg_pass_action pass_action;
} state;
static void init(void) {
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger.func = slog_func,
});
float vertices[] = {
0.0f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f
};
state.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(vertices),
});
state.pip = sg_make_pipeline(&(sg_pipeline_desc){
.shader = sg_make_shader(triangle_shader_desc(sg_query_backend())),
.layout = {
.attrs = {
[ATTR_triangle_position].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_triangle_color0].format = SG_VERTEXFORMAT_FLOAT4
}
},
});
state.pass_action = (sg_pass_action) {
.colors[0] = { .load_action=SG_LOADACTION_CLEAR, .clear_value={0.0f, 0.0f, 0.0f, 1.0f } }
};
}
void frame(void) {
sg_begin_pass(&(sg_pass){ .action = state.pass_action, .swapchain = sglue_swapchain() });
sg_apply_pipeline(state.pip);
sg_apply_bindings(&state.bind);
sg_draw(0, 3, 1);
sg_end_pass();
sg_commit();
}
void cleanup(void) {
sg_shutdown();
}
sapp_desc sokol_main(int argc, char* argv[]) {
(void)argc; (void)argv;
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
.cleanup_cb = cleanup,
.width = 640,
.height = 480,
.window_title = "Triangle",
.icon.sokol_default = true,
.logger.func = slog_func,
};
}
A minimal audio-streaming API:
A simple mono square-wave generator using the callback model:
// the sample callback, running in audio thread
static void stream_cb(float* buffer, int num_frames, int num_channels) {
assert(1 == num_channels);
static uint32_t count = 0;
for (int i = 0; i < num_frames; i++) {
buffer[i] = (count++ & (1<<3)) ? 0.5f : -0.5f;
}
}
int main() {
// init sokol-audio with default params
saudio_setup(&(saudio_desc){
.stream_cb = stream_cb,
.logger.func = slog_func,
});
// run main loop
...
// shutdown sokol-audio
saudio_shutdown();
return 0;
The same code using the push-model
#define BUF_SIZE (32)
int main() {
// init sokol-audio with default params, no callback
saudio_setup(&(saudio_desc){
.logger.func = slog_func,
});
assert(saudio_channels() == 1);
// a small intermediate buffer so we don't need to push
// individual samples, which would be quite inefficient
float buf[BUF_SIZE];
int buf_pos = 0;
uint32_t count = 0;
// push samples from main loop
bool done = false;
while (!done) {
// generate and push audio samples...
int num_frames = saudio_expect();
for (int i = 0; i < num_frames; i++) {
// simple square wave generator
buf[buf_pos++] = (count++ & (1<<3)) ? 0.5f : -0.5f;
if (buf_pos == BUF_SIZE) {
buf_pos = 0;
saudio_push(buf, BUF_SIZE);
}
}
// handle other per-frame stuff...
...
}
// shutdown sokol-audio
saudio_shutdown();
return 0;
}
Load entire files, or stream data asynchronously over HTTP (emscripten/wasm) or the local filesystem (all native platforms).
Simple C99 example loading a file into a static buffer:
#include "sokol_fetch.h"
#include "sokol_log.h"
static void response_callback(const sfetch_response*);
#define MAX_FILE_SIZE (1024*1024)
static uint8_t buffer[MAX_FILE_SIZE];
// application init
static void init(void) {
...
// setup sokol-fetch with default config:
sfetch_setup(&(sfetch_desc_t){ .logger.func = slog_func });
// start loading a file into a statically allocated buffer:
sfetch_send(&(sfetch_request_t){
.path = "hello_world.txt",
.callback = response_callback
.buffer_ptr = buffer,
.buffer_size = sizeof(buffer)
});
}
// per frame...
static void frame(void) {
...
// need to call sfetch_dowork() once per frame to 'turn the gears':
sfetch_dowork();
...
}
// the response callback is where the interesting stuff happens:
static void response_callback(const sfetch_response_t* response) {
if (response->fetched) {
// data has been loaded into the provided buffer, do something
// with the data...
const void* data = response->buffer_ptr;
uint64_t data_size = response->fetched_size;
}
// the finished flag is set both on success and failure
if (response->failed) {
// oops, something went wrong
switch (response->error_code) {
SFETCH_ERROR_FILE_NOT_FOUND: ...
SFETCH_ERROR_BUFFER_TOO_SMALL: ...
...
}
}
}
// application shutdown
static void shutdown(void) {
...
sfetch_shutdown();
...
}
Simple cross-platform time measurement:
#include "sokol_time.h"
...
/* initialize sokol_time */
stm_setup();
/* take start timestamp */
uint64_t start = stm_now();
...some code to measure...
/* compute elapsed time */
uint64_t elapsed = stm_since(start);
/* convert to time units */
double seconds = stm_sec(elapsed);
double milliseconds = stm_ms(elapsed);
double microseconds = stm_us(elapsed);
double nanoseconds = stm_ns(elapsed);
/* difference between 2 time stamps */
uint64_t start = stm_now();
...
uint64_t end = stm_now();
uint64_t elapsed = stm_diff(end, start);
/* compute a 'lap time' (e.g. for fps) */
uint64_t last_time = 0;
while (!done) {
...render something...
double frame_time_ms = stm_ms(stm_laptime(&last_time));
}
Unified argument parsing for web and native apps. Uses argc/argv on native platforms and the URL query string on the web.
Example URL with one arg:
https://floooh.github.io/tiny8bit/kc85.html?type=kc85_4
The same as command line app:
kc85 type=kc85_4
Parsed like this:
#include "sokol_args.h"
int main(int argc, char* argv[]) {
sargs_setup(&(sargs_desc){ .argc=argc, .argv=argv });
if (sargs_exists("type")) {
if (sargs_equals("type", "kc85_4")) {
// start as KC85/4
}
else if (sargs_equals("type", "kc85_3")) {
// start as KC85/3
}
else {
// start as KC85/2
}
}
sargs_shutdown();
return 0;
}
See the sokol_args.h header for a more complete documentation, and the Tiny Emulators for more interesting usage examples.
No comments yet. Set the tone — say what you would want to know.