loading...

Hello, bgfx!

pperon profile image Phil Peron ・5 min read

Bgfx is a rendering library that supports Direct3D, Metal and OpenGL variants across 11 platforms and counting. It's easy to build and there are many examples available to help submerse you in the details. Understanding the line of separation between bgfx and the example code was relatively easy but took me more time than I would have thought. If you're interested in a quick example on how to use bgfx with your own project, read on.

I assume you have some prior graphics programming experience and that you've already followed the build instructions here. I'll be borrowing from the example-01-cubes project so make sure you build using the --with-examples option if you'd like to follow along.

You'll find the debug and release libraries in a folder named something like winXX_vs20XX located inside the .build directory (making sure you link bgfx*.lib, bimg*.lib and bx*.lib). To test if all is well, call the bgfx::init function.

#include "bgfx/bgfx.h"

int main(void)
{
    bgfx::init();
    return 0;
}

With all this in place, you should be able to initialize the system without error.

We'll need a window to render to. I use GLFW but SDL or anything else will be fine.

#include "bgfx/bgfx.h"
#include "GLFW/glfw3.h"

#define WNDW_WIDTH 1600
#define WNDW_HEIGHT 900

int main(void)
{
    glfwInit();
    GLFWwindow* window = glfwCreateWindow(WNDW_WIDTH, WNDW_HEIGHT, "Hello, bgfx!", NULL, NULL);
    bgfx::init();
    return 0;
}

Now we have to make sure bgfx has a handle to the native window. This is done via the bgfx::PlatformData struct and the 'nwh' member. If you're using GLFW, make sure you define GLFW_EXPOSE_NATIVE_WIN32 and include the glfw3native header. Now is also a good time to properly define a bgfx::Init object.

...
#define GLFW_EXPOSE_NATIVE_WIN32
#include "GLFW/glfw3native.h"
...
    bgfx::PlatformData pd;
    pd.nwh = glfwGetWin32Window(window);
    bgfx::setPlatformData(pd);

    bgfx::Init bgfxInit;
    bgfxInit.type = bgfx::RendererType::Count; // Automatically choose a renderer.
    bgfxInit.resolution.width = WNDW_WIDTH;
    bgfxInit.resolution.height = WNDW_HEIGHT;
    bgfxInit.resolution.reset = BGFX_RESET_VSYNC;
    bgfx::init(bgfxInit);
...

Let's render something. We'll set the view clear flags and create a simple render loop.

...
    bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x443355FF, 1.0f, 0);
    bgfx::setViewRect(0, 0, 0, WNDW_WIDTH, WNDW_HEIGHT);

    unsigned int counter = 0;
    while(true) {   
        bgfx::frame();
        counter++;
    }
...

You should see a window with a purple background. Soak in the awesome.

At this point, we're ready to take on something more interesting. We'll steal a cube mesh from one of the example files.

struct PosColorVertex
{
    float x;
    float y;
    float z;
    uint32_t abgr;
};

static PosColorVertex cubeVertices[] =
{
    {-1.0f,  1.0f,  1.0f, 0xff000000 },
    { 1.0f,  1.0f,  1.0f, 0xff0000ff },
    {-1.0f, -1.0f,  1.0f, 0xff00ff00 },
    { 1.0f, -1.0f,  1.0f, 0xff00ffff },
    {-1.0f,  1.0f, -1.0f, 0xffff0000 },
    { 1.0f,  1.0f, -1.0f, 0xffff00ff },
    {-1.0f, -1.0f, -1.0f, 0xffffff00 },
    { 1.0f, -1.0f, -1.0f, 0xffffffff },
};

static const uint16_t cubeTriList[] =
{
    0, 1, 2,
    1, 3, 2,
    4, 6, 5,
    5, 6, 7,
    0, 2, 4,
    4, 2, 6,
    1, 5, 3,
    5, 7, 3,
    0, 4, 1,
    4, 5, 1,
    2, 3, 6,
    6, 3, 7,
};

Now we need to describe the mesh in terms of the vertex declaration, bgfx::VertexDecl.

...
    bgfx::setViewRect(0, 0, 0, WNDW_WIDTH, WNDW_HEIGHT);

    bgfx::VertexDecl pcvDecl;
    pcvDecl.begin()
        .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
        .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true)
    .end();
    bgfx::VertexBufferHandle vbh = bgfx::createVertexBuffer(bgfx::makeRef(cubeVertices, sizeof(cubeVertices)), pcvDecl);
    bgfx::IndexBufferHandle ibh = bgfx::createIndexBuffer(bgfx::makeRef(cubeTriList, sizeof(cubeTriList)));

    unsigned int counter = 0;
...

We're almost there. We just need to load a bgfx shader which we'll borrow from the example files in the examples/runtime/shaders directory. To do that we need to load the shader file contents inside a bgfx::Memory object before passing that to bgfx::createShader.

bgfx::ShaderHandle loadShader(const char *FILENAME)
{
    const char* shaderPath = "???";

    switch(bgfx::getRendererType()) {
        case bgfx::RendererType::Noop:
        case bgfx::RendererType::Direct3D9:  shaderPath = "shaders/dx9/";   break;
        case bgfx::RendererType::Direct3D11:
        case bgfx::RendererType::Direct3D12: shaderPath = "shaders/dx11/";  break;
        case bgfx::RendererType::Gnm:        shaderPath = "shaders/pssl/";  break;
        case bgfx::RendererType::Metal:      shaderPath = "shaders/metal/"; break;
        case bgfx::RendererType::OpenGL:     shaderPath = "shaders/glsl/";  break;
        case bgfx::RendererType::OpenGLES:   shaderPath = "shaders/essl/";  break;
        case bgfx::RendererType::Vulkan:     shaderPath = "shaders/spirv/"; break;
    }

    size_t shaderLen = strlen(shaderPath);
    size_t fileLen = strlen(FILENAME);
    char *filePath = (char *)malloc(shaderLen + fileLen);
    memcpy(filePath, shaderPath, shaderLen);
    memcpy(&filePath[shaderLen], FILENAME, fileLen);

    FILE *file = fopen(FILENAME, "rb");
    fseek(file, 0, SEEK_END);
    long fileSize = ftell(file);
    fseek(file, 0, SEEK_SET);

    const bgfx::Memory *mem = bgfx::alloc(fileSize + 1);
    fread(mem->data, 1, fileSize, file);
    mem->data[mem->size - 1] = '\0';
    fclose(file);

    return bgfx::createShader(mem);
}

Now we can create a shader program and wrap up rendering our cube. The bx library has matrix helper methods or use your own. Either way, building the projection matrix and setting the view transform should look familiar. Don't forget to set the vertex and index buffers and submit the program we just created before advancing the next frame.

...
    bgfx::ShaderHandle vsh = loadShader("vs_cubes.bin");
    bgfx::ShaderHandle fsh = loadShader("fs_cubes.bin");
    bgfx::ProgramHandle program = bgfx::createProgram(vsh, fsh, true);

    unsigned int counter = 0;
    while(true) {
        const bx::Vec3 at = {0.0f, 0.0f,  0.0f};
        const bx::Vec3 eye = {0.0f, 0.0f, -5.0f};
        float view[16];
        bx::mtxLookAt(view, eye, at);
        float proj[16];
        bx::mtxProj(proj, 60.0f, float(WNDW_WIDTH) / float(WNDW_HEIGHT), 0.1f, 100.0f, bgfx::getCaps()->homogeneousDepth);
        bgfx::setViewTransform(0, view, proj);

        bgfx::setVertexBuffer(0, vbh);
        bgfx::setIndexBuffer(ibh);

        bgfx::submit(0, program);
        bgfx::frame();
        counter++;
    }
...


Behold! A cube. Let's make it move.

...
    bgfx::setViewTransform(0, view, proj);
    float mtx[16];
    bx::mtxRotateXY(mtx, counter * 0.01f, counter * 0.01f);
    bgfx::setTransform(mtx);        

    bgfx::submit(0, program);
...

And we're done!

You can check out the completed example here. Note that I kept error handling and callbacks out to better highlight how bgfx is used. Hopefully this will give you a basic idea of how things work and enable you to layer in more advanced techniques. Be sure to take some time to scan through the example code and API documentation. Good luck and happy rendering!

Спасибо Бранимир Караџић!

Posted on Mar 6 '19 by:

Discussion

markdown guide
 

This line:

FILE *file = fopen(FILENAME, "rb");

Should be

FILE *file = fopen(filePath, "rb");
 

this returns null for me.
filePath ends up being "shaders/dx11/vs_cubes.binýýýý"
hard coding this to "shaders/dx11/vs_cubes.bin" results in no change despite the file being present...

Any idea why?

 

Because he uses memcpy, a null terminator never gets written to the end of the string. You need to allocate with "char* filePath = (char*)calloc(1, shaderLen + fileLen + 1);", then there will be a zero at the end of the string

 

In order to use the bgfx::setPlatformData function, you also need to include bgfx/platform.h header file.

 

bgfx::VertexDecl has been renamed to bgfx::VertexLayout.

 

For platform compatibility, you also need to add bgfx/include/compat/*** to your include paths.

For example, for Visual Studio 2012 and higher, add bgfx/include/compat/msvc to your include paths.

 

In order to get bgfx to clear the screen, you also need to call bgfx::touch(0) before bgfx::frame().

 

For bx::Vec3 you also need to include bx/math.h.

 

For FILE, and fopen function to compile, you also need to include cstdio.

 

Thanks for the great tutorial! I learned a lot about using bgfx!