πŸ€‘ Indie game storeπŸ™Œ Free gamesπŸ˜‚ Fun games😨 Horror games
πŸ‘· Game development🎨 AssetsπŸ“š Comics
πŸŽ‰ Sales🎁 Bundles

procedural

24
Posts
4
Topics
13
Followers
4
Following
A member registered Mar 13, 2016 · View creator page β†’

Tool

Recent community posts

(Edited 1 time)
If you are making use of the Mesa 17.3 releases, have you found them to be buggier than normal for this open-source 3D graphics driver stack? There remains a higher than average amount of bugs still outstanding that have plagued Mesa 17.3, even with being up to 17.3.5. 
https://www.phoronix.com/scan.php?page=news_item&px=Mesa-17.3-Remains-Buggy

Version 6 is here!

The black screen issue I had on one machine with Manjaro MATE disto and another machine with Ubuntu 18.04 and LXDE, but not on a machine with Ubuntu 18.04 and GNOME desktop environment, is fixed. It looks like some DEs reserve the last fbconfig for themselves.

I removed the strict distro requirement, but using distros other than Ubuntu is still discouraged since I currently see a possible Mesa regression bug when it doesn't generate mipmaps for cubemap images (Ubuntu 18.04's Mesa 17.2.4 works fine, Manjaro's Mesa 17.2.6 and higher have this bug).

(Edited 5 times)

Version 3 is here!

Build process and the crash on Arch Linux are fixed thanks to MārtiΕ†Ε‘ MoΕΎeiko, but the black screen bug I mentioned in the previous post is still present, so don't wait any support for Arch Linux or any other distro from me for now. But you always can try it yourself and see if it works, of course.

(Edited 14 times)

It's a Mesa bug.

Here's a screenshot of lib0 engine running with a custom Mesa build (and now Arch Linux's stock Mesa 17.2.6) on Manjaro MATE 17.0.4:

It looks normal, but it's actually broken because when I try to switch focus from a terminal to lib0 engine window the whole desktop screen turns black.

This forces me to ban any other distro that can't execute code that works just fine on Ubuntu 16.04 and Mesa 17.0.7 until a person who understands the inner workings of graphics drivers will explain this random behavior. At no point I, as a software developer, should care about any of this given the same binary runs perfectly fine under another environment.

lib0 engine requirements are updated.
(Edited 1 time)

Can confirm: crashes on Arch Linux (Manjaro MATE in my case), but not on Ubuntu 16.04. Linux user space people are all fired.

Investigating...

(Edited 1 time)

Pushed v2.4 to solve fbconfigs_count is 0 error. As for SIGSEGV bug, I logged it in  [v2] Known issues topic, will see what I can do here without being able to reproduce it. Maybe I'll install an Arch Linux and try it there, dunno.

(Edited 4 times)

Actually, for:

Thread 1 "main" received signal SIGSEGV, Segmentation fault. 0x00007fffec3f605e in GpuSysGetOpenGLProcedureAddresses () from /tmp/lib0_engine/lib0.so

You can help me by ascii recording gdb tui (with register and assembly layouts enabled) instruction stepping (si) after main.c:31 line, because I can't reproduce this issue on two different computers with the same Ubuntu 16.04.

(Edited 1 time)

Cool! Yeah, you have to remove MSAA lines for now given GLX_SAMPLE_BUFFERS: 0, GLX_SAMPLES: 0 framebuffers.

As for:

Thread 1 "main" received signal SIGSEGV, Segmentation fault. 0x00007fffec3f605e in GpuSysGetOpenGLProcedureAddresses () from /tmp/lib0_engine/lib0.so
after running through gdb

This is another bug I currently investigate and will fix today. I will also write a big post about it because it involves broken compilers and lying debuggers.

Created a new topic [all] Known issues
(Edited 24 times)

Unsolved:

  • Text is selected when scrollbars are dragged with left mouse button. Maybe TextEditor's issue, maybe an issue in Dear ImGui version lib0 engine uses. Worth looking up if it'll get too annoying.
  • For a one letter variable introduced on the current line, Clang for some unknown reason lists it in autocomplete as if it exists somewhere else already.
  • Undo is not saved between recompiles. Serialize TextEditor.h#L291-L292

Solved:

  • `TextEditor.cpp:209:32: error: use of undeclared identifier 'floor'` compile time error. Should be fixed in v2.3 or manually by adding `#include <cmath>` line at the top of TextEditor.cpp. If any of this won't help let me know!
  • GLX returns 0 framebuffers. Turns out Nvidia GPUs may not have framebuffers with MSAA samples available. Fixed in v2.4 by turning MSAA off by default.
  • SIGSEGV on Arch Linux is fixed in v3.
  • Mesa's black screen bug on some desktop environments.


    (Edited 7 times)

    Added <cmath> header to TextEditor.cpp in v2.3 release to eliminate compile time errors you get, try it! Weird that Clang 3.8 and 3.9 don't have this issue, apparently some standard header files include math for some compilers (versions?), but not for others.

    For assert issue, I'm now checking `fbconfigs_count` and `fbconfigs` pointer on 0 and NULL values to make sure it's `glXChooseFBConfig` who is failing here.

    P. S. If everything compiles correctly now, you can try to remove all asserts on reported lines to see what may happen. Probably nothing good, but you can try!

    P. P. S. Also you can try to remove lines 524 and 525 from gpulib.h, maybe there are no framebuffers which support MSAA antialiasing. Unlikely, but worth trying. Generally, you can try to remove these lines one by one from `int glx_attribs[]` struct to see if any of this will make the difference.

    If recompiling didn't made it print anything, it can mean only one thing: fbconfigs_count is equal to 0 and for loop is never executed.

    Hopefully v2.2 will fix this, if not we may have to do some GDB debugging magic :)

    Just uploaded v2.2, try it!

    $ ./main # after 1ing the static if
    Assertion failed: fbconfig != NULL (./gpulib.h: GpuSysX11Window: 582)
    Aborted (core dumped)

    Oh, I forgot to mention you have to run `/tmp/lib0_engine/build_main.sh -O0 -g` to recompile and print it!

    glxinfol....
    https://pastebin.com/qUsgGb0f

    Thank you very much! Looking at it right now...

    (Edited 1 time)

    Actually no, the logic was correct the first time, GLXFBConfig is a pointer typedef, it's 0 only when fbconfig is not choosed indeed...

    But! You can download v2.1 and change line 535 of gpulib.h from:

    #if 0

    to:

    #if 1

    It will print all the available framebuffer configs to the console, copy it and paste here for me to see where a problem might be!

    Also, could you run `glxinfo` command in a terminal and paste all the info it'll print here?

    Thanks!

    OK, I think this is my mistake! I uploaded new GpuLib commit.

    Apparently fbconfig 0 is a valid id, I think Mesa guys did this for people who don't care to iterate over all available ones. I used to check against 0 in assert, now I check against -1. This should fix the problem!

    I will upload lib0 engine v2.1 binaries right now...

    (Edited 1 time)

    Thank you for reporting this! Very interesting. It basically means this block of code (from line 510 to 547) iterated over all available framebuffers and couldn't choose any of them.

    Did it worked by replacing that assert line with `stdlib_assert(glxQueryExtension(<args omitted>) != 0)`?

    Which GPU model do you have?

    Created a new topic [v0] Alternative GUI theme
    (Edited 5 times)

    Place NotoMono-Regular.ttf to /tmp/lib0_engine/ folder. Download the font here: https://www.google.com/get/noto/#mono-mono

    Paste this code after `ImguiInit(dpy, win, scancodes);` line of `AppLoad` procedure:

    #if 1
      {
        struct ImGuiIO    * io    = igGetIO();
        struct ImGuiStyle * style = igGetStyle();
        
        style->ScrollbarRounding = 0;
        style->WindowRounding    = 0;
        style->FrameRounding     = 0;
        
        char font[10000] = {0};
        char * base_path = GpuSysGetBasePath();
        snprintf(font, 10000, "%s%s", base_path, "NotoMono-Regular.ttf");
        free(base_path);
        ImFontAtlas_AddFontFromFileTTF(io->Fonts, font, 20, NULL, NULL);
        
        static struct ImVec3 color_for_text = {211 / 255.f, 218 / 255.f, 227 / 255.f};
        static struct ImVec3 color_for_head = { 64 / 255.f, 132 / 255.f, 214 / 255.f};
        static struct ImVec3 color_for_area = { 47 / 255.f,  52 / 255.f,  63 / 255.f};
        static struct ImVec3 color_for_body = { 56 / 255.f,  60 / 255.f,  74 / 255.f};
        static struct ImVec3 color_for_pops = { 28 / 255.f,  30 / 255.f,  37 / 255.f};
        ImguiEasyTheming(color_for_text, color_for_head, color_for_area, color_for_body, color_for_pops);
        style->Colors[ImGuiCol_WindowBg] = (struct ImVec4){color_for_body.x, color_for_body.y, color_for_body.z, 0.75f};
        style->Colors[ImGuiCol_FrameBg]  = (struct ImVec4){color_for_area.x, color_for_area.y, color_for_area.z, 0.75f};
      }
    #endif

    Before:


    After:


    The control flow looks like this:

    AppInit
    AppLoad
    AppStep
    AppStep
    AppStep
    AppStep   <-- Pressed Tab
    AppUnload
    AppLoad
    AppStep
    AppStep
    AppStep   <-- Closing app
    AppUnload
    AppDeinit
    (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.

    (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`.

    (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.

    (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.

    (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.

    (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.

    (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.

    (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.

    All code is compiled with `-O0 -g` flags, by the way, so you can debug it with GDB out of the box.

    (Edited 2 times)

    Code for main.c: https://github.com/procedural/interactivec