🤑 Indie game store🙌 Free games😂 Fun games😨 Horror games
👷 Game development🎨 Assets📚 Comics
🎉 Sales🎁 Bundles

[v0] 0.c explained line by line

A topic by procedural created 89 days ago Views: 369 Replies: 8
Viewing posts 1 to 9
Developer (Edited 7 times)
#include <sys/mman.h>

This imports `mmap` procedure for virtual memory allocation.

#include <sys/stat.h>

This includes `stat` function for reading file size of 0.c file.

#include "api.h"

This includes `api_t` struct that syncs procedure names between 0.c and main.c

#define LIB0_ENGINE_MAX_SOURCE_CODE_SIZE (32 * 1024 * 1024)

Maximum bytes for the source code you type in the application. Set to 32 megabytes by default, you can increase or decrease it if needed.

#define LIB0_ENGINE_MAX_HEAP_MEMORY_SIZE (512L * 1024L * 1024L * 1024L)

Maximum heap memory bytes your application can allocate. Doesn't matter because the memory will be allocated only when needed, so set to 512 gigabytes by default.

typedef struct { float x, y, z; } vec3;

A standard 3D vector.

struct state_t {
  char               source_code[LIB0_ENGINE_MAX_SOURCE_CODE_SIZE];
  long               source_code_id;
  unsigned           vertices_id;
  vec3             * vertices;
  unsigned           cast_tex[4096];
  unsigned           indices_id;
  unsigned         * indices;
  unsigned           commands_id;
  struct gpu_cmd_t * commands;
  unsigned           textures[16];
  char               vert_string[4096];
  char               frag_string[4096];
  unsigned           vert;
  unsigned           frag;
  unsigned           ppo;
};

A giant contiguous struct that should only grow at runtime for you to declare variables you need which stay the same between code hot swaps.

Developer (Edited 2 times)

`source_code` variable is where your code you type in application lives. `source_code_id` iterates a counter to save each hot reloaded code in subsequent 1.c, 2.c, 3.c, etc files. The other variables are used for rendering a basic OpenGL triangle.

Developer (Edited 2 times)
struct state_t * s = NULL;

To share `state_t` persistent memory address between the functions such as `SourceCodeCallback`.

enum {
  cast_tex_vertices_e,
};

Enums used for `cast_tex` state variable. Just to name nameless array elements.

Developer (Edited 2 times)
static char * g_gpulib_source_code_string = NULL;

A global input parameter for `SourceCodeCallback`.

static inline int SourceCodeCallback(struct ImGuiTextEditCallbackData * data) {
  char file[4096] = {0};
  snprintf(file, 4096, "/tmp/lib0_engine/%ld.c", s->source_code_id);
  int fd = open(file, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600);
  write(fd, g_gpulib_source_code_string, strlen(g_gpulib_source_code_string));
  close(fd);
  char cmd[4096] = {0};
  snprintf(cmd, 4096,
      "clang -fPIC -O0 -g -shared -o /tmp/lib0_engine/lib0.so /tmp/lib0_engine/%ld.c /tmp/lib0_engine/libimgui.so -lX11 -lXrender -lXi -lGL -lm -ldl",
      s->source_code_id);
  s->source_code_id += 1;
  GpuSysShell(cmd, NULL);
  return 0;
}

A procedure that called when Tab key is pressed in source code editor window. Creates /tmp/lib0_engine/1.c, /tmp/lib0_engine/2.c, etc. code snapshots on hot reload. You can also see a compile command that used to create `lib0.so` shared library that swaps the previous one. You can use `gcc` instead of `clang` here if you want. `GpuSysShell` is a gpulib procedure that executes a passed string as a shell command.

Developer (Edited 2 times)
static inline void SourceCode(char * string) {
  igSetNextWindowSize((struct ImVec2){640, 360}, (1 << 2)); // ImGuiSetCond_FirstUseEver
  static bool g_gpulib_is_source_code_window_open = 1;
  igBegin("Source code", &g_gpulib_is_source_code_window_open, (1 << 11)); // ImGuiWindowFlags_HorizontalScrollbar
  g_gpulib_source_code_string = string;
  igInputTextMultiline("", string, LIB0_ENGINE_MAX_SOURCE_CODE_SIZE, (struct ImVec2){-1, -1},
                       (1 << 6), // ImGuiInputTextFlags_CallbackCompletion
                       SourceCodeCallback, NULL);
  igEnd();
}

A procedure that creates source code editor window in application using dear imgui C binding procedures which start with `ig` prefix.

static void * AppInit(Display * dpy, Window win, char * scancodes) {
  void * state = mmap(0, LIB0_ENGINE_MAX_HEAP_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0);
  s = state;
  {
    int fd = open("/tmp/lib0_engine/0.c", O_RDONLY, (mode_t)0600);
    struct stat st = {0};
    stat("/tmp/lib0_engine/0.c", &st);
    read(fd, s->source_code, st.st_size);
    close(fd);
    s->source_code_id = 1;
  }
  return state;
}

`AppInit` and `AppDeinit` are called only once when application starts and when application is about to be closed. On hot reload with Tab key `AppInit` and `AppDeinit` are not called, so write code here only if it should setup window or initialize something only once, like reading the initial 0.c file or `mmap` at the beginning of the procedure that initializes virtual memory viewed as a giant contiguous `state_t` struct.

Developer (Edited 12 times)
static void AppLoad(void * state, Display * dpy, Window win, char * scancodes) {
  s = state;
  GpuGetOpenGLProcedureAddresses();
  ImguiInit(dpy, win, scancodes);
#if 1 // Delete this block of code before the first hot reload
  s->vertices = GpuCalloc(4096 * sizeof(vec3), &s->vertices_id);
  s->indices  = GpuCallocIndices(4096, &s->indices_id);
  s->commands = GpuCallocCommands(4096, &s->commands_id);
  char vert_string[4096] = GPU_VERT_HEAD
      "layout(binding = 0) uniform samplerBuffer s_pos;" "\n"
      ""                                                 "\n"
      "void main() {"                                    "\n"
      "  vec3 pos = texelFetch(s_pos, gl_VertexID).xyz;" "\n"
      "  gl_Position = vec4(pos, 1);"                    "\n"
      "}"                                                "\n";
  char frag_string[4096] = GPU_FRAG_HEAD
      "layout(location = 0) out vec4 g_color;" "\n"
      ""                                       "\n"
      "void main() {"                          "\n"
      "  g_color = vec4(1, 0, 1, 1);"          "\n"
      "}"                                      "\n";
  for (int i = 0; i < 4096; i += 1)
    s->vert_string[i] = vert_string[i];
  for (int i = 0; i < 4096; i += 1)
    s->frag_string[i] = frag_string[i];
  s->vert = GpuVert(s->vert_string);
  s->frag = GpuFrag(s->frag_string);
  s->ppo = GpuPpo(s->vert, s->frag);
#endif
  s->vertices[0] = (vec3){ 0.0,  0.5, 0.0};
  s->vertices[1] = (vec3){ 0.5, -0.5, 0.0};
  s->vertices[2] = (vec3){-0.5, -0.5, 0.0};
  s->indices[0] = 0;
  s->indices[1] = 1;
  s->indices[2] = 2;
  s->commands[0].count = 3;
  s->commands[0].instance_count = 1;
  s->cast_tex[cast_tex_vertices_e] = GpuCast(s->vertices_id, gpu_xyz_f32_e, 0, 3 * sizeof(vec3));
  s->textures[0] = s->cast_tex[cast_tex_vertices_e];
}

`AppLoad` is called from a new shared library lib0.so loaded by main.c. This procedure is called each time Tab key is pressed, i.e. each time a hot reload lib0.so swap happens.

Let's break it apart:

s = state;

Set the state received from main.c to global variable `s`. This is needed when lib0.so is hot swapped and an address to `mmap`'ed virtual memory is lost, but main.c's `state` input parameter is still preserved (we returned `void * state` address from `AppInit` to main.c so it could survive a swap). Thus we communicate to a new lib0.so an address we got from a previous lib0.so that called `AppInit` at the start of our application.

GpuGetOpenGLProcedureAddresses();

Load OpenGL procedure pointers for each new lib0.so. We need this because OpenGL procedure pointers are declared globally for current 0.c from api.h -> gpulib_debug.h -> gpulib.h header file, see these pointers starting from line 192 in gpulib.h. Without this call calling any OpenGL procedure will result in a segfault because their addresses will be equal to 0.

ImguiInit(dpy, win, scancodes);

Reinitializes internal state of dear imgui for the new lib0.so.

#if 1 // Delete this block of code before the first hot reload
  s->vertices = GpuCalloc(4096 * sizeof(vec3), &s->vertices_id);
  s->indices  = GpuCallocIndices(4096, &s->indices_id);
  s->commands = GpuCallocCommands(4096, &s->commands_id);
  char vert_string[4096] = GPU_VERT_HEAD
      "layout(binding = 0) uniform samplerBuffer s_pos;" "\n"
      ""                                                 "\n"
      "void main() {"                                    "\n"
      "  vec3 pos = texelFetch(s_pos, gl_VertexID).xyz;" "\n"
      "  gl_Position = vec4(pos, 1);"                    "\n"
      "}"                                                "\n";
  char frag_string[4096] = GPU_FRAG_HEAD
      "layout(location = 0) out vec4 g_color;" "\n"
      ""                                       "\n"
      "void main() {"                          "\n"
      "  g_color = vec4(1, 0, 1, 1);"          "\n"
      "}"                                      "\n";
  for (int i = 0; i < 4096; i += 1)
    s->vert_string[i] = vert_string[i];
  for (int i = 0; i < 4096; i += 1)
    s->frag_string[i] = frag_string[i];
  s->vert = GpuVert(s->vert_string);
  s->frag = GpuFrag(s->frag_string);
  s->ppo = GpuPpo(s->vert, s->frag);
#endif

This block initializes GPU buffers and compiles shaders needed for triangle rendering. The reason you need to delete this or set `#if` to 0 before the first hot reload is because we want these buffers and shaders to be persistent between different lib0.so hot reloads. If you leave this buffer and shader initialization as it is, the next hot swap will reinitialize GPU buffers and shaders, deleting any data you could store from previous lib0.so hot swaps.

To put it simply, when you press Tab, the old `AppUnload` is called, the old /tmp/lib0_engine/lib0.so is replaced with a new lib0.so and a new `AppLoad` is called. Since `struct state_t * s` is preserved across lib0.so swaps, we store initialized GPU buffer ids in it, GPU buffers are still allocated between the code swaps given that `AppLoad` with `#if 1` was called the first time you run the application. You need to delete this code before the next Tab press that will call  `AppLoad` and allocate new GPU buffers, storing them in the same `state_t` variables, overwriting the previous still allocated buffers.

  s->vertices[0] = (vec3){ 0.0,  0.5, 0.0};
  s->vertices[1] = (vec3){ 0.5, -0.5, 0.0};
  s->vertices[2] = (vec3){-0.5, -0.5, 0.0};

Write 3D vertices to GPU buffers stored in `state_t`. You can change these values and press Tab to see a hot swap visually the first time you run the application.

  s->indices[0] = 0;
  s->indices[1] = 1;
  s->indices[2] = 2;

Indices which describe the order of triangle vertices. By default the winding is set to clockwise DirectX style.

  s->commands[0].count = 3;
  s->commands[0].instance_count = 1;

Setting a GPU command 0 to draw 3 vertices 1 time(s).

  s->cast_tex[cast_tex_vertices_e] = GpuCast(s->vertices_id, gpu_xyz_f32_e, 0, 3 * sizeof(vec3));

Set the first named index `cast_tex_vertices_e` of `cast_tex` state variable which holds GPU "casts", i.e. generic buffer memory to OpenGL texture ids. This is needed because in gpulib shaders receive as inputs only 2 types of data: uniforms and textures. We're describing to OpenGL that we want to "see" out generic buffer as `gpu_xyz_f32_e` (i.e. float vec3) texture that starts with byte 0 to byte `3 * sizeof(vec3)`.

  s->textures[0] = s->cast_tex[cast_tex_vertices_e];

Set to texture slot 0 (`layout(binding = 0)` in the vertex shader) our "casted" buffer (now: texture id) that stores our triangle vertices.

Developer (Edited 1 time)
static int AppStep(void * state, Display * dpy, Window win, char * scancodes) {
  s = state;
  for (XEvent event = {0}; XPending(dpy);) {
    XNextEvent(dpy, &event);
    ImguiProcessEvent(&event);
    switch (event.type) {
      break; case ClientMessage: {
        if (event.xclient.data.l[0] == XInternAtom(dpy, "WM_DELETE_WINDOW", 0))
          return 1;
      }
    }
  }
  ImguiNewFrame();
  if (GpuDebugFrag(&s->frag, s->frag_string, sizeof(s->frag_string))) {
    glDeleteProgramPipelines(1, &s->ppo);
    s->ppo = GpuPpo(s->vert, s->frag);
  }
  GpuClear();
  GpuBindPpo(s->ppo);
  GpuBindTextures(0, 16, s->textures);
  GpuBindIndices(s->indices_id);
  GpuBindCommands(s->commands_id);
  GpuDraw(gpu_triangles_e, 0, 1);
  SourceCode(s->source_code);
  igRender();
  GpuSwap(dpy, win);
  return 0;
}

`AppStep` is called on each frame. This is where you pull input events or draw triangles or do whatever you want on each frame. Change it as you want!

static void AppUnload(void * state) {
  s = state;
  glDeleteTextures(4096, s->cast_tex);
  ImguiDeinit();
}

`AppUnload` is called before a new lib0.so is used. It deallocates whatever data previous lib0.so allocated in `AppLoad` or in `AppStep`.

static void AppDeinit(void * state) {
  s = state;
  munmap(state, LIB0_ENGINE_MAX_HEAP_MEMORY_SIZE);
}

`AppDeinit` is called only when application is about to be closed (user clicked close button or pressed Alt + F4). It deallocates whatever data was allocated in `AppInit`.

Developer (Edited 2 times)
struct api_t APP_API = {
  .Init   = AppInit,
  .Load   = AppLoad,
  .Step   = AppStep,
  .Unload = AppUnload,
  .Deinit = AppDeinit,
};

`APP_API` is a struct read by main.c (line 25) to know which new lib0.so procedure addresses to call.

Developer

The control flow looks like this:

AppInit
AppLoad
AppStep
AppStep
AppStep
AppStep   <-- Pressed Tab
AppUnload
AppLoad
AppStep
AppStep
AppStep   <-- Closing app
AppUnload
AppDeinit