Convert Figma logo to code with AI

floooh logosokol

minimal cross-platform standalone C headers

6,830
477
6,830
137

Top Related Projects

21,434

A simple and easy-to-use library to enjoy videogames programming

59,344

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies

9,077

A single-header ANSI C immediate mode cross-platform GUI library

3,408

A tiny immediate-mode UI library

Emscripten: An LLVM-to-WebAssembly Compiler

9,377

Simple Directmedia Layer

Quick Overview

Sokol is a minimalist, cross-platform library for developing graphical applications and games. It provides a set of single-file libraries that can be easily integrated into C and C++ projects, offering simple APIs for graphics, audio, and application lifecycle management across multiple platforms.

Pros

  • Simple and lightweight, with each module contained in a single header file
  • Cross-platform support for Windows, macOS, Linux, iOS, Android, and web browsers (via WebAssembly)
  • Easy integration into existing projects without complex build systems
  • Provides a consistent API across different platforms and rendering backends (OpenGL, Metal, D3D11, WebGL)

Cons

  • Limited feature set compared to more comprehensive game engines or frameworks
  • Requires more low-level programming knowledge compared to higher-level game development tools
  • Documentation could be more extensive, especially for beginners
  • May require additional libraries for more advanced functionality (e.g., physics, networking)

Code Examples

  1. Initializing a window and rendering context:
#define SOKOL_IMPL
#define SOKOL_GLCORE33
#include "sokol_app.h"
#include "sokol_gfx.h"

sapp_desc sokol_main(int argc, char* argv[]) {
    return (sapp_desc){
        .width = 800,
        .height = 600,
        .init_cb = init,
        .frame_cb = frame,
        .cleanup_cb = cleanup,
        .window_title = "Sokol Example",
    };
}
  1. Creating and rendering a triangle:
void init(void) {
    sg_setup(&(sg_desc){0});
    
    float vertices[] = {
        -0.5f, -0.5f, 0.0f,
         0.5f, -0.5f, 0.0f,
         0.0f,  0.5f, 0.0f
    };
    
    sg_buffer vbuf = sg_make_buffer(&(sg_buffer_desc){
        .data = SG_RANGE(vertices)
    });
    
    state.pip = sg_make_pipeline(&(sg_pipeline_desc){
        .shader = sg_make_shader(simple_shader_desc()),
        .layout = {
            .attrs[0] = { .format = SG_VERTEXFORMAT_FLOAT3 }
        },
        .primitive_type = SG_PRIMITIVETYPE_TRIANGLES
    });
}
  1. Handling input events:
void input(const sapp_event* ev) {
    if (ev->type == SAPP_EVENTTYPE_KEY_DOWN) {
        if (ev->key_code == SAPP_KEYCODE_ESCAPE) {
            sapp_request_quit();
        }
    }
}

Getting Started

To get started with Sokol, follow these steps:

  1. Download the required header files from the Sokol GitHub repository.
  2. Include the necessary headers in your project (e.g., sokol_app.h, sokol_gfx.h).
  3. Implement the sokol_main function as the entry point for your application.
  4. Define callbacks for initialization, frame rendering, and cleanup.
  5. Compile and link your project with the appropriate backend libraries (e.g., OpenGL, Metal).

Example minimal setup:

#define SOKOL_IMPL
#define SOKOL_GLCORE33
#include "sokol_app.h"
#include "sokol_gfx.h"

void init(void) {
    sg_setup(&(sg_desc){0});
}

void frame(void) {
    sg_begin_default_pass(&(sg_pass_action){0}, sapp_width(), sapp_height());
    sg_end_pass();
    sg_commit();
}

void cleanup(void) {
    sg_shutdown();
}

sapp_desc sokol_main(int argc, char* argv[]) {
    return (sapp_desc){

Competitor Comparisons

21,434

A simple and easy-to-use library to enjoy videogames programming

Pros of raylib

  • More comprehensive feature set, including audio, 3D support, and built-in math functions
  • Extensive documentation and examples, making it easier for beginners to get started
  • Cross-platform support with official bindings for multiple programming languages

Cons of raylib

  • Larger codebase and dependencies, which may increase compile times and binary size
  • Less flexibility for integrating with existing projects or custom rendering pipelines
  • Opinionated API design that may not suit all programming styles or project requirements

Code Comparison

raylib:

#include "raylib.h"

int main() {
    InitWindow(800, 450, "raylib example");
    while (!WindowShouldClose()) {
        BeginDrawing();
        ClearBackground(RAYWHITE);
        DrawText("Hello, raylib!", 190, 200, 20, LIGHTGRAY);
        EndDrawing();
    }
    CloseWindow();
    return 0;
}

sokol:

#include "sokol_app.h"
#include "sokol_gfx.h"

sapp_desc sokol_main(int argc, char* argv[]) {
    return (sapp_desc){
        .init_cb = init,
        .frame_cb = frame,
        .cleanup_cb = cleanup,
        .width = 800,
        .height = 450,
        .window_title = "sokol example",
    };
}
59,344

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies

Pros of ImGui

  • Mature and widely adopted UI library with extensive documentation
  • Rich set of UI elements and customization options
  • Cross-platform support with multiple backend implementations

Cons of ImGui

  • Focused solely on immediate mode GUI, less versatile for other graphics tasks
  • Steeper learning curve for developers new to immediate mode GUI concepts
  • Larger codebase and potentially higher memory footprint

Code Comparison

ImGui (C++):

ImGui::Begin("Hello, ImGui!");
if (ImGui::Button("Click me!"))
    doSomething();
ImGui::End();

Sokol (C):

sg_begin_default_pass(&pass_action, width, height);
sgl_begin_default_pass(&pass_action, width, height);
// Draw your custom UI here
sgl_end();
sg_end_pass();

Key Differences

  • ImGui is specifically designed for creating user interfaces, while Sokol is a more general-purpose graphics library
  • Sokol provides a lower-level API for graphics programming, giving developers more control over rendering
  • ImGui offers a higher-level abstraction for UI creation, making it easier to quickly prototype interfaces

Use Cases

  • Choose ImGui for projects requiring complex user interfaces with minimal setup
  • Opt for Sokol when building graphics applications that need fine-grained control over rendering and resource management
9,077

A single-header ANSI C immediate mode cross-platform GUI library

Pros of Nuklear

  • More comprehensive UI toolkit with a wider range of pre-built widgets
  • Highly customizable appearance and theming options
  • Extensive documentation and examples

Cons of Nuklear

  • Larger codebase and potentially higher memory footprint
  • Steeper learning curve for beginners
  • May require more setup and configuration

Code Comparison

Nuklear:

struct nk_context ctx;
nk_init_default(&ctx, 0);

if (nk_begin(&ctx, "Demo", nk_rect(50, 50, 200, 200), NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|NK_WINDOW_SCALABLE|NK_WINDOW_MINIMIZABLE|NK_WINDOW_TITLE)) {
    nk_layout_row_dynamic(&ctx, 30, 1);
    if (nk_button_label(&ctx, "Button")) {
        // Button clicked
    }
}
nk_end(&ctx);

Sokol:

sg_pass_action pass_action = {0};
sg_begin_default_pass(&pass_action, sapp_width(), sapp_height());

sgl_defaults();
sgl_begin_triangles();
sgl_v2f_c3b( 0.0f,  0.5f, 255, 0, 0);
sgl_v2f_c3b(-0.5f, -0.5f, 0, 255, 0);
sgl_v2f_c3b( 0.5f, -0.5f, 0, 0, 255);
sgl_end();

sg_end_pass();
sg_commit();
3,408

A tiny immediate-mode UI library

Pros of microui

  • Extremely lightweight and minimalistic (single-header library)
  • Focused solely on immediate mode GUI, making it simpler to use for basic UI needs
  • Easy to integrate into existing projects due to its small footprint

Cons of microui

  • Limited feature set compared to Sokol's comprehensive toolkit
  • Less active development and smaller community support
  • Lacks cross-platform abstractions for graphics, audio, and input handling

Code Comparison

microui example:

mu_begin(ctx);
if (mu_begin_window(ctx, "My Window", mu_rect(10, 10, 200, 200))) {
  mu_label(ctx, "Hello world!");
  mu_end_window(ctx);
}
mu_end(ctx);

Sokol example (using sokol_gfx.h):

sg_begin_default_pass(&pass_action, width, height);
// ... rendering code ...
sg_end_pass();
sg_commit();

Summary

microui is a lightweight, single-header immediate mode GUI library, while Sokol is a more comprehensive toolkit for cross-platform game development. microui is simpler to integrate but has fewer features, whereas Sokol provides a wider range of functionality including graphics, audio, and input handling abstractions across multiple platforms.

Emscripten: An LLVM-to-WebAssembly Compiler

Pros of Emscripten

  • Comprehensive toolchain for compiling C/C++ to WebAssembly
  • Large ecosystem and community support
  • Extensive documentation and examples

Cons of Emscripten

  • Steeper learning curve for beginners
  • Larger codebase and more complex setup
  • May include unnecessary features for simple projects

Code Comparison

Emscripten (C++ to JavaScript):

#include <emscripten.h>
#include <stdio.h>

int main() {
    printf("Hello, Emscripten!\n");
    return 0;
}

Sokol (C header-only library):

#define SOKOL_IMPL
#include "sokol_app.h"
#include "sokol_gfx.h"

sapp_desc sokol_main(int argc, char* argv[]) {
    return (sapp_desc){ .width = 640, .height = 480, .title = "Sokol App" };
}

Key Differences

  • Emscripten focuses on compiling C/C++ to WebAssembly, while Sokol is a lightweight, header-only library for cross-platform development
  • Emscripten provides a full toolchain, whereas Sokol offers minimal abstractions for common tasks
  • Sokol is more suitable for small, performance-critical applications, while Emscripten excels in larger, complex projects requiring web integration
9,377

Simple Directmedia Layer

Pros of SDL

  • More comprehensive feature set, including audio, input handling, and networking
  • Wider platform support, including mobile and console platforms
  • Larger community and ecosystem with extensive documentation and resources

Cons of SDL

  • Larger codebase and potentially higher overhead
  • More complex API with steeper learning curve
  • Slower release cycle and potential for legacy code maintenance

Code Comparison

SDL:

SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);

Sokol:

sg_setup(&(sg_desc){0});
sapp_desc app_desc = {
    .width = 640,
    .height = 480,
    .window_title = "Sokol Window",
    .init_cb = init,
    .frame_cb = frame,
};
sapp_run(&app_desc);

The code comparison shows that SDL requires more explicit initialization and management of window and renderer objects, while Sokol provides a more streamlined setup process with callback-based rendering.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README



Simple STB-style cross-platform libraries for C and C++, written in C.

Sokol

See what's new (02-Sep-2024 in sokol_gfx.h, SG_FILTER_NONE has been removed)

Build Bindings build build OdinRustDlang

Examples and Related Projects

Core libraries

  • sokol_gfx.h: 3D-API wrapper (GL/GLES3/WebGL2 + Metal + D3D11 + WebGPU)
  • sokol_app.h: app framework wrapper (entry + window + 3D-context + input)
  • sokol_time.h: time measurement
  • sokol_audio.h: minimal buffer-streaming audio playback
  • sokol_fetch.h: asynchronous data streaming from HTTP and local filesystem
  • sokol_args.h: unified cmdline/URL arg parser for web and native apps
  • sokol_log.h: provides a standard logging callback for the other sokol headers

Utility libraries

'Official' Language Bindings

These are automatically updated on changes to the C headers:

Notes

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.

Why C:

  • easier integration with other languages
  • easier integration into other projects
  • adds only minimal size overhead to executables

A blog post with more background info: A Tour of sokol_gfx.h

sokol_gfx.h:

  • simple, modern wrapper around GLES3/WebGL2, GL3.3, D3D11, Metal, and WebGPU
  • buffers, images, shaders, pipeline-state-objects and render-passes
  • does not handle window creation or 3D API context initialization
  • does not provide shader dialect cross-translation (BUT there's now an 'official' shader-cross-compiler solution which seamlessly integrates with sokol_gfx.h and IDEs: see here for details

sokol_app.h

A minimal cross-platform application-wrapper library:

  • unified application entry
  • single window or canvas for 3D rendering
  • 3D context initialization
  • event-based keyboard, mouse and touch input
  • supported platforms: Win32, MacOS, Linux (X11), iOS, WASM, Android, UWP
  • supported 3D-APIs: GL3.3 (GLX/WGL), Metal, D3D11, GLES3/WebGL2

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_vs_position].format = SG_VERTEXFORMAT_FLOAT3,
                [ATTR_vs_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,
    };
}

sokol_audio.h

A minimal audio-streaming API:

  • you provide a mono- or stereo-stream of 32-bit float samples which sokol_audio.h forwards into platform-specific backends
  • two ways to provide the data:
    1. directly fill backend audio buffer from your callback function running in the audio thread
    2. alternatively push small packets of audio data from your main loop, or a separate thread created by you
  • platform backends:
    • Windows: WASAPI
    • macOS/iOS: CoreAudio
    • Linux: ALSA
    • emscripten: WebAudio + ScriptProcessorNode (doesn't use the emscripten-provided OpenAL or SDL Audio wrappers)

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

sokol_fetch.h

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();
    ...
}

sokol_time.h:

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

sokol_args.h

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.