Skip to content

Instantly share code, notes, and snippets.

@seyhajin
Last active February 22, 2023 11:21
Show Gist options
  • Save seyhajin/632026e3c4e5bbf517e54ebc052f63ca to your computer and use it in GitHub Desktop.
Save seyhajin/632026e3c4e5bbf517e54ebc052f63ca to your computer and use it in GitHub Desktop.
Minimal sprite rendering example written in C with `SDL2` for windowing, `sokol_gfx` for graphics API using WebGL2/GLES3, `stb_image` for loading image, compiled in WebAssembly with Emscripten

webgl2-wasm-sdl-sokol-sprite

Minimal sprite rendering example written in C with SDL2 for windowing, sokol_gfx for graphics API using WebGL2/GLES3, stb_image for loading image, compiled in WebAssembly with Emscripten.

Step by step

  1. Clone the repositories sokol and stb in the deps folder
  2. Add an image (sky.png) in assets directory
  3. Compile and build with the following command (need Emscripten installed):
emcc sprite.c -o sprite.html -Ideps/sokol -Ideps/stb --preload-file assets -s EXIT_RUNTIME=1 -s ALLOW_MEMORY_GROWTH=1 -s USE_WEBGL2=1 -s USE_SDL=2
  1. Launch a web server from the output folder
  2. Open sprite.hml page from web server

Dependencies

  • sdl2 (builtin in Emscripten)
  • sokol (#21c9d47)
  • stb (#8b5f1f3)
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
// emscripten
#include "emscripten.h"
// sdl
#include "SDL2/SDL.h"
// sokol
#define SOKOL_IMPL
//#define SOKOL_DEBUG
#define SOKOL_GLES3
#include "sokol_gfx.h"
// stb_image
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
// macros
#define CODE(...) #__VA_ARGS__
// globals
SDL_Window* sdl_window;
SDL_GLContext gl_context;
static struct {
sg_pipeline pip;
sg_bindings bind;
sg_pass_action pass_action;
} state = {
.pass_action.colors[0] = { .action = SG_ACTION_CLEAR, .value = { 0.0f, 0.0f, 0.0f, 1.0f } }
};
//-----------------------------------------
// frame callback
//-----------------------------------------
void frame() {
/* process events */
// input();
/* render and draw */
int32_t w, h;
SDL_GL_GetDrawableSize(sdl_window, &w, &h);
sg_begin_default_pass(&state.pass_action, w, h);
sg_apply_pipeline(state.pip);
sg_apply_bindings(&state.bind);
sg_draw(0, 6, 1);
sg_end_pass();
sg_commit();
SDL_GL_SwapWindow(sdl_window);
}
//-----------------------------------------
// main entry point
//-----------------------------------------
int main(int argc, const char* argv[]) {
/* setup sdl */
SDL_Init(SDL_INIT_EVERYTHING);
sdl_window = SDL_CreateWindow(
"webgl2-wasm-sdl-sokol-sprite",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
800, 600,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
);
assert(sdl_window);
/* create opengles context on sdl window */
#if defined(SOKOL_GLES2)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#elif defined(SOKOL_GLES3)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif
gl_context = SDL_GL_CreateContext(sdl_window);
SDL_GL_MakeCurrent(sdl_window, gl_context);
SDL_GL_SetSwapInterval(0);
assert(gl_context);
/* get sdl & gl infos */
SDL_version sdl_compiled, sdl_linked;
SDL_VERSION(&sdl_compiled);
SDL_GetVersion(&sdl_linked);
printf("SDL version (compiled): %d.%d.%d\n", sdl_compiled.major, sdl_compiled.minor, sdl_compiled.patch);
printf("SDL version (linked): %d.%d.%d\n", sdl_linked.major, sdl_linked.minor, sdl_linked.patch);
const GLubyte* gl_vendor = glGetString(GL_VENDOR);
printf("GL Vendor: %s\n", gl_vendor);
const GLubyte* gl_renderer = glGetString(GL_RENDERER);
printf("GL Renderer: %s\n", gl_renderer);
const GLubyte* gl_version = glGetString(GL_VERSION);
printf("GL Version: %s\n", gl_version);
const GLubyte* glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION);
printf("GLSL Version: %s\n", glsl_version);
/* setup sokol_gfx */
sg_setup(&(sg_desc){0});
assert(sg_isvalid());
const float vertices[] = {
// pos // uv
-0.5f, 0.5f, 0.0, 1.0,
0.5f, 0.5f, 1.0, 1.0,
0.5f, -0.5f, 1.0, 0.0,
-0.5f, -0.5f, 0.0, 0.0,
};
state.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(vertices)
});
const uint16_t indices[] = {
0, 1, 2,
0, 2, 3
};
state.bind.index_buffer = sg_make_buffer(&(sg_buffer_desc){
.type = SG_BUFFERTYPE_INDEXBUFFER,
.data = SG_RANGE(indices),
});
/* load sprite image from file */
int w, h, n;
stbi_set_flip_vertically_on_load(true);
unsigned char *pixels = stbi_load("assets/sky.png", &w, &h, &n, STBI_rgb_alpha);
assert(pixels);
state.bind.fs_images[0] = sg_make_image(&(sg_image_desc){
.width = w,
.height = h,
.data.subimage[0][0] = {
.ptr = pixels,
.size = (size_t)(w * h * 4),
}
});
stbi_image_free(pixels);
sg_shader shd = sg_make_shader(&(sg_shader_desc){
.attrs = {
[0].name = "position",
[1].name = "texcoord0"
},
.fs.images[0] = {
.name = "tex",
.image_type = SG_IMAGETYPE_2D
},
.vs.source = "#version 300 es\n" CODE(
precision mediump float;
in vec2 position;
in vec2 texcoord0;
out vec2 uv;
void main(){
gl_Position = vec4(position, 0.0, 1.0);
uv = texcoord0;
}
),
.fs.source = "#version 300 es\n" CODE(
precision mediump float;
uniform sampler2D tex;
in vec2 uv;
out vec4 frag_color;
void main(){
frag_color = texture(tex, uv);
}
)
});
state.pip = sg_make_pipeline(&(sg_pipeline_desc){
.shader = shd,
.primitive_type = SG_PRIMITIVETYPE_TRIANGLES,
.index_type = SG_INDEXTYPE_UINT16,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
[1].format = SG_VERTEXFORMAT_FLOAT2,
}
},
.depth = {
.compare = SG_COMPAREFUNC_LESS_EQUAL,
.write_enabled = true
},
.cull_mode = SG_CULLMODE_BACK,
});
/* main frame loop */
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(frame, 0, 1);
#else
while(true)
frame();
#endif
sg_shutdown();
SDL_GL_DeleteContext(gl_context);
SDL_DestroyWindow(sdl_window);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment