Frame of 42yeah

Site Defunct

注意!截止到 16/9/2019 ,这个博客已经被搬迁到了 这里 。以后我的东西都会发在那里。拜拜啦!

啊啊啊,bgfx!

bgfx 是一套极度可怕的跨平台图形库。在挺多年前就出生了,但是 bgfx 到现在还有看起来不愠不火的热度。但究竟是不是真的是这样呢?

经过了贼久的折磨和反复尝试外加读示例代码之后,让我们一起来看看 bgfx 宏伟壮观的 hello world 吧!

先决条件

在我们开始之前,要知道 bgfx 有非常大一堆的依赖。

Android NDK

下载 NDK 。然后导出环境变量

setx ANDROID_NDK_ROOT <path to AndroidNDK directory>
setx ANDROID_NDK_ARM <path to AndroidNDK directory>\toolchains\arm-linux-androideabi-4.7\prebuilt\windows-x86_64
setx ANDROID_NDK_MIPS <path to AndroidNDK directory>\toolchains\mipsel-linux-android-4.7\prebuilt\windows-x86_64
setx ANDROID_NDK_X86 <path to AndroidNDK directory>\toolchains\x86-4.7\prebuilt\windows-x86_64

嗯……其他系统自便吧……

Linux 上

sudo apt-get install libgl1-mesa-dev x11proto-core-dev libx11-dev

Windows 上

Hello world!

当然,万事开头 clone!bgfx 有俩依赖,都是作者自己写的:bimg, bx。

git clone https://github.com/bkaradzic/bgfx
git clone https://github.com/bkaradzic/bx
git clone https://github.com/bkaradzic/bimg
cd bgfx

进入完 bgfx 目录之后,我们开始 make:

make build-windows # windows
make vs2017-release64 # visual studio 
make linux-release64 # linux
make osx-release64 # osx 

对应的系统就 make 对应的东西。如果你不确定自己 make 啥,直接打 make 就好了……

make 大概是不会出现什么错误的。我自己只在 macOS 上试过,没有错,我就大胆的估计其他也没错了。但是要注意,bimg 和 bx 必须要和 bgfx 在同一个目录下, make 才会成功!

现在我们正常来说已经有了构造好的例子了(我不知道别的系统,反正 macOS 有了)。macOS 用户们!咱可以在下一句测试 bgfx 是不是已经构造好了:

cd examples/runtime
../../.build/osx64_clang/bin/examples.app/Contents/MacOS/examplesRelease

BGFX 出来啦!

噩梦的开始

好了。接下来你看到了一堆静态库,你以为已经好了。bgfx 已经可以用了。没错,他的确可以用了……但是这才是噩梦的开始……官方没有任何文档说怎么用。唯一能参考的东西就是 examples 里面的 common 文件夹,因为很明显这些 examples 都被高度的通用化过了,里边的代码贼跨平台,并且依赖就是 common 。很明显,在真正的环境里,我们不可以永远的用他的 common (并且在 mac 内因为 Metal ,(我也不知道为什么)他会告诉你找不到 _main 。如果是自己的项目,要通过编译好的 common 库的编译必须手动关掉 bgfx 的 common 的 Metal 支持。我知道这个链接的 issue 不是这样说的,但是按照这样做的确可以解决问题。) 。因此,我们怎么样摆脱 exmamples/common ,建立自己的 bgfx 渲染上下文呢?

解决方案

经过了好几天的读代码 / xjb 尝试之后,我终于终于能让他渲染起来了……真的崩了,怎么一点文档也没有啊……

但是!无论如何,这就是从 glfw 渲染的方法了!

解决方案 A,GLFW 创建窗体,别的全盘托管给 BGFX

bgfx 的示例 common 库里面的窗体实现方法是每一个平台 (Windows, Mac, Linux, WebAssembly, 诸如此类)都自己实现了创建窗体 / 上下文的方法。既然这样,我们为什么不用跨平台的、接口轻便又舒服的、稳定很多的 GLFW 来管理窗口和输入呢?其实完全没问题!所以我们马上开始吧!

首先,我们先写出他的骨架:

#include <bgfx/bgfx.h>
#include <bgfx/platform.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>


glm::ivec2 winSize(GLFWwindow *win) {
    glm::ivec2 size;
    
    glfwGetWindowSize(win, &size.x, &size.y);
    return size;
}


int main(void) {
    glfwInit();
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    
    // 看见这个标题的时候,你已经知道我当时基本上很绝望了……
    GLFWwindow *win = glfwCreateWindow(800, 600, "Yet Another BGFX test", nullptr, nullptr); 
    
    while (!glfwWindowShouldClose(win)) {
        glfwPollEvents();
        glfwSwapBuffers(win);
    }
    
    return 0;
}

我这里偷懒,用了 glm 。当然 winSize 那自己改改也是可以的啦。

注意,在这里面,我根本就没有用 glfw 来创建 OpenGL Context 。没有各种的 glfwWindowHint ,就一个 GLFW_NO_API 。没有各种的 glew, glad, glut 。这些东西 bgfx 自己完全有能力搞定。

然后,在 GLFWwindow *win = glfwCreateWindow(800, 600, "Yet Another BGFX test", nullptr, nullptr); 这一行之后,我们就要开始初始化 bgfx 的上下文了:

    bgfxSetWindow(win); // 注意 bgfxSetWindow 必须在 bgfx::init 之前!

    bgfx::Init initializer;
    
    initializer.type = bgfx::RendererType::OpenGL; // 你还可以选 Vulkan, Metal, ...
    initializer.resolution.reset = BGFX_RESET_VSYNC; // 垂直同步
    initializer.resolution.width = winSize(win).x;
    initializer.resolution.height = winSize(win).y;
    initializer.vendorId = BGFX_PCI_ID_NONE; // 你可以选 BGFX_PCI_ID_NVIDIA, BGFX_PCI_ID_AMD, ... NONE 就是选第一张显卡
    
    bgfx::init(initializer); // 初始化 BGFX!
    
    bgfx::setDebug(BGFX_DEBUG_TEXT); // 开启 DEBUG 文本

    bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x0000ffff); // 美妙的蓝色

注意中间的那句 bgfxSetWindow ,这是 bgfx 本身没有的。这里我们要开一个新的文件,我把它叫做 StolenEntry.hpp ,因为我是从 这里 拿的……

#ifndef StolenEntry_h
#define StolenEntry_h

#include <iostream>

#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#if GLFW_VERSION_MINOR < 2
#    error "GLFW 3.2 or later is required"
#endif // GLFW_VERSION_MINOR < 2

#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
#    define GLFW_EXPOSE_NATIVE_X11
#    define GLFW_EXPOSE_NATIVE_GLX
#elif BX_PLATFORM_OSX
#    define GLFW_EXPOSE_NATIVE_COCOA
#    define GLFW_EXPOSE_NATIVE_NSGL
#elif BX_PLATFORM_WINDOWS
#    define GLFW_EXPOSE_NATIVE_WIN32
#    define GLFW_EXPOSE_NATIVE_WGL
#endif //
#include <GLFW/glfw3native.h>

#include <bgfx/bgfx.h>
#include <bgfx/platform.h>


static void* glfwNativeWindowHandle(GLFWwindow* _window)
{
#    if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
    return (void*)(uintptr_t)glfwGetX11Window(_window);
#    elif BX_PLATFORM_OSX
    return glfwGetCocoaWindow(_window);
#    elif BX_PLATFORM_WINDOWS
    return glfwGetWin32Window(_window);
#    endif // BX_PLATFORM_
}

void bgfxSetWindow(GLFWwindow* _window)
{
    bgfx::PlatformData pd;
#    if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
    pd.ndt      = glfwGetX11Display();
#    elif BX_PLATFORM_OSX
    pd.ndt      = NULL;
#    elif BX_PLATFORM_WINDOWS
    pd.ndt      = NULL;
#    endif // BX_PLATFORM_WINDOWS
    pd.nwh          = glfwNativeWindowHandle(_window);
    pd.context      = NULL;
    pd.backBuffer   = NULL;
    pd.backBufferDS = NULL;
    
    bgfx::setPlatformData(pd); // 刚刚说注意 bgfxSetWindow 必须在 bgfx::init 之前,现在更确切的说,是 bgfx::setPlatformData 必须在 bgfx::init 之前
}


#endif /* StolenEntry_h */

回到 main.cpp ,include 一下这个,好了,你大概认为你现在运行这片代码可以看见一片蓝色了……但是不行!这都是心酸的泪啊…… bgfx 自己要在渲染循环里面加上独特的句子。改完以后,渲染循环长这样:

    while (!glfwWindowShouldClose(win)) {
        glm::ivec2 winSiz = winSize(win);
        bgfx::setViewRect(0, 0, 0, winSiz.x, winSiz.y); // 设 ViewPort 
        
        glfwPollEvents();
        bgfx::touch(0); // 清屏本体!
        bgfx::dbgTextClear(); // 清字
        
        bgfx::dbgTextPrintf(0, 0, 0x0f, "Hello world, BGFX!"); // 写字在 (0, 0)

        bgfx::frame(); // 渲染!
        glfwSwapBuffers(win); // 交换缓冲区
    }

注意啊啊啊啊! bgfx::touch(0) 才是清屏的本体啊!不是 bgfx::setViewClear,那是设置怎么清屏的…… 我曾经以为 touch 只是微不足道的一句话而已,原来不是这样的……

glfwSwapBuffers(win) 好像是不需要的,我去了一样能渲染出来。但是没有进一步的尝试,我不敢肯定。

但是无论如何,你现在就应该可以看见美妙的文本了!

60 年代既视感!

解决方案 2,GLFW 托管 OpenGL 上下文,BGFX 只管绘制

这次,GLFW 负责创建上下文了:

#include <iostream>
#include <bgfx/bgfx.h>
#include <bgfx/platform.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>


glm::ivec2 winSize(GLFWwindow *win) {
    glm::ivec2 size;
    
    glfwGetWindowSize(win, &size.x, &size.y);
    return size;
}


int main(void) {
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    
    GLFWwindow *win = glfwCreateWindow(800, 600, "Yet Another BGFX", nullptr, nullptr);
    glfwMakeContextCurrent(win);

    while (!glfwWindowShouldClose(win)) {
        glfwPollEvents();
        glfwSwapBuffers(win);
    }
    
    return 0;
}

我们可以看到,glfwWindowHint 和他的各种东西出现了,当然还有 glfwMakeContextCurrent 。其次要干啥呢,咱要在 glfwMakeContextCurrent(win); 后面加上:

    // 变成这样
    bgfx::PlatformData pd = bgfxGetPlatformData(win);
    pd.context = glfwGetNSGLContext(win); // 注意!在 Mac 里才是这种方法拿 context !在别的系统里不是,自行对号入座!
    bgfx::setPlatformData(bgfxGetPlatformData(win));
    
    bgfx::Init initializer;
    
    
    initializer.type = bgfx::RendererType::OpenGL;
    initializer.resolution.reset = BGFX_RESET_VSYNC;
    initializer.resolution.width = winSize(win).x;
    initializer.resolution.height = winSize(win).y;
    initializer.vendorId = BGFX_PCI_ID_NONE;
    
    bgfx::init(initializer);
    
    bgfx::setDebug(BGFX_DEBUG_TEXT);
    
    bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x0000ffff);

如果你看过解决方案 1 的……解决方案的话,就会发现原本的一行变成了三行,然后还多出了一个 bgfxGetPlatformData(win) 这个方法。其实这个函数是我对 StolenEntry.hppbgfxSetWindow 的一个小改,为了适配没有看解决方案 1 的人,下面是 StolenEntry.hpp 的源码:

#ifndef StolenEntry_h
#define StolenEntry_h

#include <iostream>

#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#if GLFW_VERSION_MINOR < 2
#    error "GLFW 3.2 or later is required"
#endif // GLFW_VERSION_MINOR < 2

#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
#    define GLFW_EXPOSE_NATIVE_X11
#    define GLFW_EXPOSE_NATIVE_GLX
#elif BX_PLATFORM_OSX
#    define GLFW_EXPOSE_NATIVE_COCOA
#    define GLFW_EXPOSE_NATIVE_NSGL
#elif BX_PLATFORM_WINDOWS
#    define GLFW_EXPOSE_NATIVE_WIN32
#    define GLFW_EXPOSE_NATIVE_WGL
#endif //
#include <GLFW/glfw3native.h>

#include <bgfx/bgfx.h>
#include <bgfx/platform.h>


static void* glfwNativeWindowHandle(GLFWwindow* _window)
{
#    if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
    return (void*)(uintptr_t)glfwGetX11Window(_window);
#    elif BX_PLATFORM_OSX
    return glfwGetCocoaWindow(_window);
#    elif BX_PLATFORM_WINDOWS
    return glfwGetWin32Window(_window);
#    endif // BX_PLATFORM_
}

void bgfxSetWindow(GLFWwindow* _window)
{
    bgfx::PlatformData pd;
#    if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
    pd.ndt      = glfwGetX11Display();
#    elif BX_PLATFORM_OSX
    pd.ndt      = NULL;
#    elif BX_PLATFORM_WINDOWS
    pd.ndt      = NULL;
#    endif // BX_PLATFORM_WINDOWS
    pd.nwh          = glfwNativeWindowHandle(_window);
    pd.context      = NULL;
    pd.backBuffer   = NULL;
    pd.backBufferDS = NULL;
    
    bgfx::setPlatformData(pd);
}


bgfx::PlatformData bgfxGetPlatformData(GLFWwindow* _window)
{
    bgfx::PlatformData pd;
#    if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
    pd.ndt      = glfwGetX11Display();
#    elif BX_PLATFORM_OSX
    pd.ndt      = NULL;
#    elif BX_PLATFORM_WINDOWS
    pd.ndt      = NULL;
#    endif // BX_PLATFORM_WINDOWS
    pd.nwh          = glfwNativeWindowHandle(_window);
    pd.context      = NULL;
    pd.backBuffer   = NULL;
    pd.backBufferDS = NULL;
    
    return pd;
}


#endif /* StolenEntry_h */

可以看到,bgfxGetPlatformDatabgfxSetWindow 的区别只是把原本的 bgfx::setPlatformData(pd); 换成了 return pd; ,并且返回值由 void 变成了 bgfx::PlatformData 而已。这么做是因为在 main 里头,我们还要进一步更改 pd 的值 —— 设置 pd.context —— 也就是我们早已经由 GLFW 搞好的 OpenGL 上下文。

完事儿了以后,我们再改他的绘制循环:

    while (!glfwWindowShouldClose(win)) {
        glm::ivec2 winSiz = winSize(win);
        bgfx::setViewRect(0, 0, 0, winSiz.x, winSiz.y);
        
        glfwPollEvents();
        bgfx::dbgTextClear();
        bgfx::touch(0);
        
        bgfx::dbgTextPrintf(0, 0, 0x0f, "Hello world, BGFX!");

        bgfx::frame();
    }

运行起来的话,你看到的界面应该和解决方案 A 一毛一样:

还是 60 年代的既视感……

完成!

尾声

这才是 hello world ……这也太虐了吧……不过我觉得学会了 bgfx 也是有他血赚的地方的,首先啦,你就再也不用学任何和 OpenGL Vulkan Metal DirectX 还有不知道未来会出现什么也不知道作者会不会更新到那里的各种图形学 API 了。其次,好像 bgfx 的代码看起来简单那么一点?

但是无论如何,这篇好长也好虐啊……但我毕竟已经走上不归路了……对不对?

大家晚安!

评论区