Using bgfx from C

As you may know I’m not much of a C++ fan, the whole point of source code is it should be human readable and it doesn’t take much to make C++ code look like someone’s sicked up a load of ascii on your code! and besides its much more fun being closer to the metal…

Despite bgfx being primarily coded in C++ an auto generated C wrapper is maintained for the benefit of people wrapping the library for other languages. I decided to have a good look at this, and happily its a close fit to using it with C++ and handily the api documents do show the C function equivalents for all the calls. In many cases its a trivial renaming of a call and light fettling of parameters. Porting the previous C++ example to C was basically trivial, but I did get buffer creation wrong at first…

There are no math functions wrapped, which is a shame but a quick trip onto github and I grabbed the first single header math “library” I could find… I’m not quite sure if this is the source of my winding issues (I’ve had to reverse the winding order for my indices) but it is my prime suspect! I used the math_3d.h from here

unsurprisingly! you need to include a different header for bgfx if you’re using it from C

#include <bgfx/c99/bgfx.h>

The first gottcha is that just 1 structure requires a constructor call, it would have to be my favourite the init structure!

    bgfx_init_t bgfxInit;
    bgfx_init_ctor(&bgfxInit);

Hardly onerous as long as you don’t forget it!

Defining the buffer layouts slightly different, but again its a trivial change

    bgfx_vertex_layout_begin(&pcvDecl, bgfx_get_renderer_type());
    bgfx_vertex_layout_add(&pcvDecl, BGFX_ATTRIB_POSITION, 3, BGFX_ATTRIB_TYPE_FLOAT, false, false);
    bgfx_vertex_layout_add(&pcvDecl, BGFX_ATTRIB_COLOR0, 4, BGFX_ATTRIB_TYPE_UINT8, true, false);
    bgfx_vertex_layout_end(&pcvDecl);
    
    const bgfx_memory_t* vbm =bgfx_copy(cubeVertices, sizeof(PosColourVertex) * 8);
    bgfx_vertex_buffer_handle_t vbh = bgfx_create_vertex_buffer(vbm, &pcvDecl, BGFX_BUFFER_NONE);
    
    const bgfx_memory_t* ibm = bgfx_copy(cubeTriList, sizeof(uint16_t) * 12 * 3);
    bgfx_index_buffer_handle_t ibh = bgfx_create_index_buffer(ibm, BGFX_BUFFER_NONE);

note its important to use bgfx_copy or bad things will happen, I found looking at python code using bgfx to be quite a useful guide.

submitting buffers to be rendered is little different too here I’m drawing a rotating cube at the world origin and a static cube off to one side, it would appear all you need to do is set the transform and buffers and you can resubmit as you like

        mat4_t mtx,mtx_x,mtx_y;
        mtx_y = m4_rotation_y(counter * 0.007f);
        mtx_x = m4_rotation_x(counter * 0.01f);
        mtx = m4_mul(mtx_x, mtx_y);
        bgfx_set_transform(&mtx, 1);
        
        bgfx_set_vertex_buffer(0, vbh, 0, 8);
        bgfx_set_index_buffer(ibh, 0, 12*3);
    
        bgfx_submit(0, program, 0, BGFX_DISCARD_ALL);

        
        mtx = m4_translation((vec3_t){3,0,0});
        bgfx_set_transform(&mtx,1);
        bgfx_set_vertex_buffer(0, vbh, 0, 8);
        bgfx_set_index_buffer(ibh, 0, 12*3);
            
        bgfx_submit(0, program, 0, BGFX_DISCARD_ALL);
       

and really there isn’t too much more to be said, thankfully it seems fairly easy to port C++ code using bgfx to C, which is indeed a blessing! For reference here is the complete code, although you can compile your code with -std=c99 don’t forget at the end of the process you’ll have to link in the standard C++ library (-lstdc++) , as is common when using a C++ library

#include <stdio.h>
#include <string.h>
#include <limits.h>

#include <bgfx/c99/bgfx.h>

#include "GLFW/glfw3.h"

#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD 
    #define GLFW_EXPOSE_NATIVE_X11
#else
    #define GLFW_EXPOSE_NATIVE_WIN32
#endif

#include "GLFW/glfw3native.h"

#define MATH_3D_IMPLEMENTATION
#include "math_3d.h"


#define WNDW_WIDTH 960
#define WNDW_HEIGHT 540

typedef struct PosColourVertex
{
    float x;
    float y;
    float z;
    uint32_t abgr;
} PosColourVertex;

static PosColourVertex cubeVertices[] =
{
    {-1.0f,  1.0f,  1.0f, 0xff888888 },
    { 1.0f,  1.0f,  1.0f, 0xff8888ff },
    {-1.0f, -1.0f,  1.0f, 0xff88ff88 },
    { 1.0f, -1.0f,  1.0f, 0xff88ffff },
    {-1.0f,  1.0f, -1.0f, 0xffff8888 },
    { 1.0f,  1.0f, -1.0f, 0xffff88ff },
    {-1.0f, -1.0f, -1.0f, 0xffffff88 },
    { 1.0f, -1.0f, -1.0f, 0xffffffff },
};

// TODO why did I need to change winding order ?
static const uint16_t cubeTriList[] =
{
    2, 1, 0,
    2, 3, 1,
    5, 6, 4,
    7, 6, 5,
    4, 2, 0,
    6, 2, 4,
    3, 5, 1,
    3, 7, 5,
    1, 4, 0,
    1, 5, 4,
    6, 3, 2,
    7, 3, 6,
};

bgfx_shader_handle_t loadShader(const char *FILENAME)
{
    const char* shaderPath = "???";

    //dx11/  dx9/   essl/  glsl/  metal/ pssl/  spirv/
    bgfx_shader_handle_t invalid = BGFX_INVALID_HANDLE;
    
    switch(bgfx_get_renderer_type()) {
        case BGFX_RENDERER_TYPE_NOOP:
        case BGFX_RENDERER_TYPE_DIRECT3D9:     shaderPath = "shaders/dx9/";   break;
        case BGFX_RENDERER_TYPE_DIRECT3D11:
        case BGFX_RENDERER_TYPE_DIRECT3D12:    shaderPath = "shaders/dx11/";  break;
        case BGFX_RENDERER_TYPE_GNM:           shaderPath = "shaders/pssl/";  break;
        case BGFX_RENDERER_TYPE_METAL:         shaderPath = "shaders/metal/"; break;
        case BGFX_RENDERER_TYPE_OPENGL:        shaderPath = "shaders/glsl/";  break;
        case BGFX_RENDERER_TYPE_OPENGLES:      shaderPath = "shaders/essl/";  break;
        case BGFX_RENDERER_TYPE_VULKAN:        shaderPath = "shaders/spirv/"; break;
        case BGFX_RENDERER_TYPE_NVN:
        case BGFX_RENDERER_TYPE_WEBGPU:
        case BGFX_RENDERER_TYPE_COUNT:         return invalid; // count included to keep compiler warnings happy
    }

    size_t shaderLen = strlen(shaderPath);
    size_t fileLen = strlen(FILENAME);
    char *filePath = (char *)malloc(shaderLen + fileLen + 1);
    memcpy(filePath, shaderPath, shaderLen);
    memcpy(&filePath[shaderLen], FILENAME, fileLen);
    filePath[shaderLen+fileLen] = 0;    // properly null terminate

    FILE *file = fopen(filePath, "rb");
    
    if (!file) {
        return invalid;
    }
    
    fseek(file, 0, SEEK_END);
    long fileSize = ftell(file);
    fseek(file, 0, SEEK_SET);

    const bgfx_memory_t *mem = bgfx_alloc(fileSize + 1);
    fread(mem->data, 1, fileSize, file);
    mem->data[mem->size - 1] = '\0';
    fclose(file);

    return bgfx_create_shader(mem);
}

int main(void)
{
    glfwInit();
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    GLFWwindow* window = glfwCreateWindow(WNDW_WIDTH, WNDW_HEIGHT, "Hello, bgfx!", NULL, NULL);

    bgfx_platform_data_t pd;
    
    #if BX_PLATFORM_LINUX || BX_PLATFORM_BSD 
    
        #if ENTRY_CONFIG_USE_WAYLAND // examples entry options define
            pd.ndt      = glfwGetWaylandDisplay(); 
        #else 
            pd.ndt      = glfwGetX11Display(); 
            pd.nwh = (void*)glfwGetX11Window(window);
        #endif 
        
    #elif BX_PLATFORM_OSX
     
            pd.ndt      = NULL; 
    
    #elif BX_PLATFORM_WINDOWS 
    
            pd.ndt      = NULL; 
            pd.nwh = glfwGetWin32Window(window);
    
    #endif // BX_PLATFORM_*

    bgfx_init_t bgfxInit;
    bgfx_init_ctor(&bgfxInit);
    
    bgfxInit.type = BGFX_RENDERER_TYPE_COUNT; // pick one!
    
    bgfxInit.type = BGFX_RENDERER_TYPE_VULKAN;
    
    bgfxInit.resolution.width = WNDW_WIDTH;
    bgfxInit.resolution.height = WNDW_HEIGHT;
    bgfxInit.resolution.reset = BGFX_RESET_VSYNC;
    
    // seems bgfx is bright enough to not use the active but unused integrated device!
    //bgfxInit.vendorId = BGFX_PCI_ID_NVIDIA; // just in case its selecting unused integrated device?
    
    bgfxInit.platformData = pd;
    bgfx_init(&bgfxInit);
    
    printf("using %s renderer\n",bgfx_get_renderer_name(bgfx_get_renderer_type()));

    bgfx_set_view_clear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x443355FF, 1.0f, 0);
    bgfx_set_view_rect(0, 0, 0, WNDW_WIDTH, WNDW_HEIGHT);

    bgfx_vertex_layout_t pcvDecl;
    
    bgfx_vertex_layout_begin(&pcvDecl, bgfx_get_renderer_type());
    bgfx_vertex_layout_add(&pcvDecl, BGFX_ATTRIB_POSITION, 3, BGFX_ATTRIB_TYPE_FLOAT, false, false);
    bgfx_vertex_layout_add(&pcvDecl, BGFX_ATTRIB_COLOR0, 4, BGFX_ATTRIB_TYPE_UINT8, true, false);
    bgfx_vertex_layout_end(&pcvDecl);
    
    const bgfx_memory_t* vbm =bgfx_copy(cubeVertices, sizeof(PosColourVertex) * 8);
    bgfx_vertex_buffer_handle_t vbh = bgfx_create_vertex_buffer(vbm, &pcvDecl, BGFX_BUFFER_NONE);
    
    const bgfx_memory_t* ibm = bgfx_copy(cubeTriList, sizeof(uint16_t) * 12 * 3);
    bgfx_index_buffer_handle_t ibh = bgfx_create_index_buffer(ibm, BGFX_BUFFER_NONE);

    bgfx_shader_handle_t vsh = loadShader("vs_cubes.bin");
    printf("shader handle %i created for vs_cubes.bin\n", vsh.idx);
    if (vsh.idx == USHRT_MAX)
    {
        printf("*** shader model not supported or file not found ****\n");
        bgfx_shutdown();
        return -1;
    }

    bgfx_shader_handle_t fsh = loadShader("fs_cubes.bin");
    printf("shader handle %i created for fs_cubes.bin \n", fsh.idx);
    if (fsh.idx == USHRT_MAX)
    {
        printf("*** shader model not supported or file not found ****\n");
        bgfx_shutdown();
        return -1;
    }
    
    bgfx_program_handle_t program = bgfx_create_program(vsh, fsh, true);
    printf("program handle %i created\n", program.idx);

    unsigned int counter = 0;
    while(!glfwWindowShouldClose(window)) 
    {
        // Set view 0 default viewport.
        bgfx_set_view_rect(0, 0, 0, (uint16_t)WNDW_WIDTH, (uint16_t)WNDW_HEIGHT);
   
        vec3_t at = {0.0f, 0.0f,  0.0f};
        vec3_t eye = (vec3_t){sin((float)counter/100)*8, 2, cos((float)counter/100)*8};
        vec3_t up = {0.0f, 1.0f, 0.0f};
        mat4_t view;

        view = m4_look_at( eye, at, up );

        mat4_t proj;
        proj = m4_perspective(60.0f, (float)(WNDW_WIDTH) / (float)(WNDW_HEIGHT),  0.01f, 1000.f);
        
        bgfx_set_view_transform(0, &view, &proj);

        mat4_t mtx,mtx_x,mtx_y;
        mtx_y = m4_rotation_y(counter * 0.007f);
        mtx_x = m4_rotation_x(counter * 0.01f);
        mtx = m4_mul(mtx_x, mtx_y);
        bgfx_set_transform(&mtx, 1);
        
        bgfx_set_vertex_buffer(0, vbh, 0, 8);
        bgfx_set_index_buffer(ibh, 0, 12*3);
    
        bgfx_submit(0, program, 0, BGFX_DISCARD_ALL);

        
        mtx = m4_translation((vec3_t){3,0,0});
        bgfx_set_transform(&mtx,1);
        bgfx_set_vertex_buffer(0, vbh, 0, 8);
        bgfx_set_index_buffer(ibh, 0, 12*3);
            
        bgfx_submit(0, program, 0, BGFX_DISCARD_ALL);
        
        bgfx_frame(false);
        
        counter++;
        
        glfwPollEvents();
        if ( glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        {
            glfwSetWindowShouldClose(window, true);
        }
    }

    bgfx_destroy_program(program);
    
    bgfx_destroy_index_buffer(ibh);
    bgfx_destroy_vertex_buffer(vbh);
    bgfx_shutdown();       
    
    glfwDestroyWindow(window);
    glfwTerminate();

    return 0;

}

Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *