Skip to content

Instantly share code, notes, and snippets.

@RichardGale
Created May 16, 2017 22:15
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save RichardGale/6e2b74bc42b3005e08397236e4be0fd0 to your computer and use it in GitHub Desktop.
Save RichardGale/6e2b74bc42b3005e08397236e4be0fd0 to your computer and use it in GitHub Desktop.
imgui+bgfx
// ImGui BFFX binding
// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID in imgui.cpp.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown().
// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
#include <imgui.h>
#include "imgui_impl_bgfx.h"
// BGFX/BX
#include "bgfx/bgfx.h"
#include "bgfx/embedded_shader.h"
#include "bx/fpumath.h"
#include "bx/timer.h"
// Data
static uint8_t g_View = 255;
static int64_t g_Time = 0;
static bgfx::TextureHandle g_FontTexture = BGFX_INVALID_HANDLE;
static bgfx::ProgramHandle g_ShaderHandle = BGFX_INVALID_HANDLE;
static bgfx::UniformHandle g_AttribLocationTex = BGFX_INVALID_HANDLE;
static bgfx::VertexDecl g_VertexDecl;
// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure)
// If text or lines are blurry when integrating ImGui in your engine:
// - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f)
void ImGui_Implbgfx_RenderDrawLists(ImDrawData* draw_data)
{
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
ImGuiIO& io = ImGui::GetIO();
int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x);
int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y);
if (fb_width == 0 || fb_height == 0)
return;
draw_data->ScaleClipRects(io.DisplayFramebufferScale);
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
uint64_t state = BGFX_STATE_RGB_WRITE | BGFX_STATE_ALPHA_WRITE | BGFX_STATE_MSAA | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA);
// Setup viewport, orthographic projection matrix
float ortho[16];
bx::mtxOrtho(ortho, 0.0f, io.DisplaySize.x, io.DisplaySize.y, 0.0f, -1.0f, 1.0f);
bgfx::setViewTransform(g_View, NULL, ortho);
bgfx::setViewRect(g_View, 0, 0, (uint16_t)fb_width, (uint16_t)fb_height);
// Render command lists
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
uint32_t idx_buffer_offset = 0;
bgfx::TransientVertexBuffer tvb;
bgfx::TransientIndexBuffer tib;
uint32_t numVertices = (uint32_t)cmd_list->VtxBuffer.size();
uint32_t numIndices = (uint32_t)cmd_list->IdxBuffer.size();
if ((numVertices != bgfx::getAvailTransientVertexBuffer(numVertices, g_VertexDecl)) ||
(numIndices != bgfx::getAvailTransientIndexBuffer(numIndices)))
{
// not enough space in transient buffer just quit drawing the rest...
break;
}
bgfx::allocTransientVertexBuffer(&tvb, numVertices, g_VertexDecl);
bgfx::allocTransientIndexBuffer(&tib, numIndices);
ImDrawVert* verts = (ImDrawVert*)tvb.data;
memcpy(verts, cmd_list->VtxBuffer.begin(), numVertices * sizeof(ImDrawVert));
ImDrawIdx* indices = (ImDrawIdx*)tib.data;
memcpy(indices, cmd_list->IdxBuffer.begin(), numIndices * sizeof(ImDrawIdx));
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback)
{
pcmd->UserCallback(cmd_list, pcmd);
}
else
{
const uint16_t xx = (uint16_t)bx::fmax(pcmd->ClipRect.x, 0.0f);
const uint16_t yy = (uint16_t)bx::fmax(pcmd->ClipRect.y, 0.0f);
bgfx::setScissor(xx, yy, (uint16_t)bx::fmin(pcmd->ClipRect.z, 65535.0f)-xx, (uint16_t)bx::fmin(pcmd->ClipRect.w, 65535.0f)-yy);
bgfx::setState(state);
bgfx::TextureHandle texture = { (uint16_t)((intptr_t)pcmd->TextureId&0xffff) };
bgfx::setTexture(0, g_AttribLocationTex, texture);
bgfx::setVertexBuffer(&tvb, 0, numVertices);
bgfx::setIndexBuffer(&tib, idx_buffer_offset, pcmd->ElemCount);
bgfx::submit(g_View, g_ShaderHandle);
}
idx_buffer_offset += pcmd->ElemCount;
}
}
}
bool ImGui_Implbgfx_CreateFontsTexture()
{
// Build texture atlas
ImGuiIO& io = ImGui::GetIO();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
// Upload texture to graphics system
g_FontTexture = bgfx::createTexture2D((uint16_t)width,
(uint16_t)height,
false,
1,
bgfx::TextureFormat::BGRA8,
0,
bgfx::copy(pixels, width*height*4));
// Store our identifier
io.Fonts->TexID = (void *)(intptr_t)g_FontTexture.idx;
return true;
}
#include "vs_imgui.bin.h"
#include "fs_imgui.bin.h"
static const bgfx::EmbeddedShader s_embeddedShaders[] =
{
BGFX_EMBEDDED_SHADER(vs_ocornut_imgui),
BGFX_EMBEDDED_SHADER(fs_ocornut_imgui),
BGFX_EMBEDDED_SHADER_END()
};
bool ImGui_Implbgfx_CreateDeviceObjects()
{
bgfx::RendererType::Enum type = bgfx::getRendererType();
g_ShaderHandle = bgfx::createProgram(bgfx::createEmbeddedShader(s_embeddedShaders, type, "vs_ocornut_imgui"),
bgfx::createEmbeddedShader(s_embeddedShaders, type, "fs_ocornut_imgui"),
true);
g_VertexDecl.begin()
.add(bgfx::Attrib::Position, 2, bgfx::AttribType::Float)
.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
.add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true)
.end();
g_AttribLocationTex = bgfx::createUniform("g_AttribLocationTex", bgfx::UniformType::Int1);
ImGui_Implbgfx_CreateFontsTexture();
return true;
}
void ImGui_Implbgfx_InvalidateDeviceObjects()
{
bgfx::destroyUniform(g_AttribLocationTex);
bgfx::destroyProgram(g_ShaderHandle);
if (isValid(g_FontTexture))
{
bgfx::destroyTexture(g_FontTexture);
ImGui::GetIO().Fonts->TexID = 0;
g_FontTexture.idx = bgfx::invalidHandle;
}
}
void ImGui_Implbgfx_Init(int view)
{
g_View = (uint8_t)(view&0xff);
ImGuiIO& io = ImGui::GetIO();
// io.KeyMap[ImGuiKey_Tab] = (int)entry::Key::Tab;
// io.KeyMap[ImGuiKey_LeftArrow] = (int)entry::Key::Left;
// io.KeyMap[ImGuiKey_RightArrow] = (int)entry::Key::Right;
// io.KeyMap[ImGuiKey_UpArrow] = (int)entry::Key::Up;
// io.KeyMap[ImGuiKey_DownArrow] = (int)entry::Key::Down;
// io.KeyMap[ImGuiKey_Home] = (int)entry::Key::Home;
// io.KeyMap[ImGuiKey_End] = (int)entry::Key::End;
// io.KeyMap[ImGuiKey_Delete] = (int)entry::Key::Delete;
// io.KeyMap[ImGuiKey_Backspace] = (int)entry::Key::Backspace;
// io.KeyMap[ImGuiKey_Enter] = (int)entry::Key::Return;
// io.KeyMap[ImGuiKey_Escape] = (int)entry::Key::Esc;
// io.KeyMap[ImGuiKey_A] = (int)entry::Key::KeyA;
// io.KeyMap[ImGuiKey_C] = (int)entry::Key::KeyC;
// io.KeyMap[ImGuiKey_V] = (int)entry::Key::KeyV;
// io.KeyMap[ImGuiKey_X] = (int)entry::Key::KeyX;
// io.KeyMap[ImGuiKey_Y] = (int)entry::Key::KeyY;
// io.KeyMap[ImGuiKey_Z] = (int)entry::Key::KeyZ;
io.RenderDrawListsFn = ImGui_Implbgfx_RenderDrawLists; // Alternatively you can set this to NULL and call ImGui::GetDrawData() after ImGui::Render() to get the same ImDrawData pointer.
g_Time = bx::getHPCounter();
}
void ImGui_Implbgfx_Shutdown()
{
ImGui_Implbgfx_InvalidateDeviceObjects();
ImGui::Shutdown();
}
void ImGui_Implbgfx_NewFrame(float width,
float height,
float mouse_x,
float mouse_y,
bool mousePressed0,
bool mousePressed1,
bool mousePressed2,
float mouseWheel,
int key)
{
if (!isValid(g_FontTexture))
ImGui_Implbgfx_CreateDeviceObjects();
ImGuiIO& io = ImGui::GetIO();
// Setup display size (every frame to accommodate for window resizing)
io.DisplaySize = ImVec2(width, height);
// Setup time step
int64_t current_time = bx::getHPCounter();
const double freq = (double)bx::getHPFrequency();
io.DeltaTime = (float)((current_time - g_Time)/freq);
g_Time = current_time;
// Setup inputs
io.MousePos = ImVec2(mouse_x, mouse_y);
io.MouseDown[0] = mousePressed0;
io.MouseDown[1] = mousePressed1;
io.MouseDown[2] = mousePressed2;
io.MouseWheel = mouseWheel;
if (key < 0x7f)
{
io.AddInputCharacter(key);
}
// Start the frame
ImGui::NewFrame();
}
// ImGui BGFX binding
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown().
// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
void ImGui_Implbgfx_Init(int view);
void ImGui_Implbgfx_Shutdown();
void ImGui_Implbgfx_NewFrame(float width,
float height,
float mouse_x,
float mouse_y,
bool mousePressed0,
bool mousePressed1,
bool mousePressed2,
float mouseWheel,
int key);
// Use if you want to reset your rendering device without losing ImGui state.
void ImGui_Implbgfx_InvalidateDeviceObjects();
bool ImGui_Implbgfx_CreateDeviceObjects();
@pr0g
Copy link

pr0g commented May 8, 2020

Hi @RichardGale,

I found this Gist super helpful getting setup with bgfx and Dear ImGui. Thank you very much for sharing it!

As both bgfx and Dear ImGui have undergone some API updates since this was first posted I've created a fork with a bunch of small fixes to get things compiling and running today - https://gist.github.com/pr0g/aff79b71bf9804ddb03f39ca7c0c3bbb

The first revision I pushed includes the most minimal subset of changes to get things working again, I will likely continue to update it more in future.

(Update 2020/11/01: I've updated the Gist again with a small set of changes to have this bgfx implementation work with the latest Dear ImGui changes - ocornut/imgui@0f13fdd).

Thanks again!

Tom

@ddengster
Copy link

Since v1.86, according to https://github.com/ocornut/imgui/releases/tag/v1.86 if you get visual glitches when using popups change this line L93 to

bgfx::setIndexBuffer(&tib, pcmd->IdxOffset, pcmd->ElemCount);

@pr0g
Copy link

pr0g commented Aug 14, 2022

I just tested this locally and you're 100% correct. I could see the visual glitch before and then saw it disappear after. I've updated my fork of this Gist here with the changes. Thanks for reporting it! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment