Skip to content

Instantly share code, notes, and snippets.

@carloscm
Last active June 13, 2024 11:35
Show Gist options
  • Save carloscm/36324265dd14b7934f1fef5ccf977e26 to your computer and use it in GitHub Desktop.
Save carloscm/36324265dd14b7934f1fef5ccf977e26 to your computer and use it in GitHub Desktop.
bgfx renderer for nuklear. Bring your own event handling and your own common sense for integration into your project.
#include "../platform/platform.h"
#include <cstdlib>
#include <cstring>
#include <bgfx/bgfx.h>
#include <bgfx/embedded_shader.h>
#include <bx/allocator.h>
#include <bx/math.h>
#define NK_IMPLEMENTATION
#include "bgfx-nuklear.h"
struct nk_bgfx_vertex {
float position[2];
float uv[2];
nk_byte col[4];
};
#include "shaders/vs_nuklear.inc"
#include "shaders/fs_nuklear.inc"
static const bgfx::EmbeddedShader embedded_shaders_nuklear[] = {
BGFX_EMBEDDED_SHADER(vs_nuklear_shader),
BGFX_EMBEDDED_SHADER(fs_nuklear_shader),
BGFX_EMBEDDED_SHADER_END()
};
void nk_bgfx_init(nk_bgfx& nkb) {
nk_init_default(&nkb.ctx, 0);
nk_buffer_init_default(&nkb.cmds);
//sdl.ctx.clip.copy = nk_bgfx_clipboard_copy;
//sdl.ctx.clip.paste = nk_bgfx_clipboard_paste;
//sdl.ctx.clip.userdata = nk_handle_ptr(0);
bgfx::RendererType::Enum type = bgfx::getRendererType();
nkb.shader = bgfx::createProgram(
bgfx::createEmbeddedShader(embedded_shaders_nuklear, type, "vs_nuklear_shader"),
bgfx::createEmbeddedShader(embedded_shaders_nuklear, type, "fs_nuklear_shader"),
true
);
assert(nkb.shader.idx != bgfx::kInvalidHandle);
nkb.s_texture = bgfx::createUniform("s_texture", bgfx::UniformType::Sampler);
nkb.vertex_decl.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();
#if 0
size_t layer_bytes = 256 * 256 * 4;
const bgfx::Memory* mem = bgfx::alloc(uint32_t(layer_bytes));
memset(mem->data, 0, layer_bytes);
mem->data[0] = 0xff;
mem->data[1] = 0xff;
mem->data[2] = 0xff;
mem->data[3] = 0xff;
nkb.texture = bgfx::createTexture2D(
256, 256,
false,
1,
bgfx::TextureFormat::RGBA8,
0,
mem
);
nkb.null.texture.id = nkb.texture.idx;
nkb.null.uv.x = 0.0f;
nkb.null.uv.y = 0.0f;
#endif
// TEST: builtin font atlas
nk_font_atlas_init_default(&nkb.atlas);
nk_font_atlas_begin(&nkb.atlas);
struct nk_font_config cfg = nk_font_config(0);
cfg.oversample_h = 3;
cfg.oversample_v = 2;
nkb.font = nk_font_atlas_add_from_file(&nkb.atlas,
emdom::platform::path::resource("fonts/NotoSans-Regular.ttf").c_str(),
18, &cfg);
const void* image; int w, h;
image = nk_font_atlas_bake(&nkb.atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
size_t layer_bytes = w * h * 4;
const bgfx::Memory* mem = bgfx::alloc(uint32_t(layer_bytes));
memcpy(mem->data, image, layer_bytes);
nkb.texture = bgfx::createTexture2D(
w, h,
false,
1,
bgfx::TextureFormat::RGBA8,
0,
mem
);
nk_font_atlas_end(&nkb.atlas, nk_handle_id(nkb.texture.idx), &nkb.null);
/*if (nkb.atlas.default_font) {
nk_style_set_font(&nkb.ctx, &nkb.atlas.default_font->handle);
}*/
nk_style_set_font(&nkb.ctx, &nkb.font->handle);
}
void nk_bgfx_destroy(nk_bgfx& nkb) {
nk_font_atlas_clear(&nkb.atlas);
nk_free(&nkb.ctx);
nk_buffer_free(&nkb.cmds);
bgfx::destroy(nkb.texture);
bgfx::destroy(nkb.shader);
bgfx::destroy(nkb.s_texture);
memset(&nkb, 0, sizeof(nk_bgfx));
}
void nk_bgfx_render(
nk_bgfx& nkb,
int view_id,
int width,
int height
) {
const bgfx::Caps* caps = bgfx::getCaps();
{
float ortho[16];
bx::mtxOrtho(ortho, 0.0f, float(width), float(height), 0.0f, 0.0f, 1000.0f, 0.0f, caps->homogeneousDepth);
bgfx::setViewTransform(view_id, NULL, ortho);
bgfx::setViewRect(view_id, 0, 0, uint16_t(width), uint16_t(height));
}
/* convert from command queue into draw list and draw to screen */
struct nk_buffer vbuf, ebuf;
/* fill convert configuration */
struct nk_convert_config config;
static const struct nk_draw_vertex_layout_element vertex_layout[] = {
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(nk_bgfx_vertex, position)},
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(nk_bgfx_vertex, uv)},
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(nk_bgfx_vertex, col)},
{NK_VERTEX_LAYOUT_END}
};
NK_MEMSET(&config, 0, sizeof(config));
config.vertex_layout = vertex_layout;
config.vertex_size = sizeof(nk_bgfx_vertex);
config.vertex_alignment = NK_ALIGNOF(nk_bgfx_vertex);
config.null = nkb.null;
config.circle_segment_count = 22;
config.curve_segment_count = 22;
config.arc_segment_count = 22;
config.global_alpha = 1.0f;
config.shape_AA = NK_ANTI_ALIASING_OFF;
config.line_AA = NK_ANTI_ALIASING_OFF;
#define MAX_VERTEX_COUNT 65536
#define MAX_VERTEX_MEMORY nkb.vertex_decl.getSize(MAX_VERTEX_COUNT)
#define MAX_ELEMENT_COUNT (MAX_VERTEX_COUNT * 2)
#define MAX_ELEMENT_MEMORY (MAX_ELEMENT_COUNT * sizeof(uint16_t))
bgfx::TransientVertexBuffer tvb;
bgfx::TransientIndexBuffer tib;
bgfx::allocTransientVertexBuffer(&tvb, MAX_VERTEX_COUNT, nkb.vertex_decl);
bgfx::allocTransientIndexBuffer(&tib, MAX_ELEMENT_COUNT);
nk_buffer_init_fixed(&vbuf, tvb.data, (nk_size)MAX_VERTEX_MEMORY);
nk_buffer_init_fixed(&ebuf, tib.data, (nk_size)MAX_ELEMENT_MEMORY);
nk_convert(&nkb.ctx, &nkb.cmds, &vbuf, &ebuf, &config);
/* iterate over and execute each draw command */
uint32_t offset = 0;
const struct nk_draw_command* cmd;
nk_draw_foreach(cmd, &nkb.ctx, &nkb.cmds) {
if (!cmd->elem_count) {
continue;
}
uint64_t state = 0
| BGFX_STATE_WRITE_RGB
| BGFX_STATE_WRITE_A
| BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA)
//| BGFX_STATE_MSAA
;
bgfx::TextureHandle th = nkb.texture;
th.idx = cmd->texture.id;
bgfx::ProgramHandle program = nkb.shader;
const uint16_t xx = uint16_t(bx::max(cmd->clip_rect.x, 0.0f));
const uint16_t yy = uint16_t(bx::max(cmd->clip_rect.y, 0.0f));
const uint16_t cw = uint16_t(bx::max(cmd->clip_rect.w, 0.0f));
const uint16_t ch = uint16_t(bx::max(cmd->clip_rect.h, 0.0f));
bgfx::setScissor(xx, yy, cw, ch);
bgfx::setState(state);
bgfx::setTexture(0, nkb.s_texture, th);
bgfx::setVertexBuffer(0, &tvb, 0, MAX_VERTEX_COUNT);
bgfx::setIndexBuffer(&tib, offset, cmd->elem_count);
bgfx::submit(view_id, program);
offset += cmd->elem_count;
}
nk_buffer_clear(&nkb.cmds);
nk_clear(&nkb.ctx);
}
//static void
//nk_bgfx_clipboard_paste(nk_handle usr, struct nk_text_edit* edit)
//{
// const char* text = SDL_GetClipboardText();
// if (text) nk_textedit_paste(edit, text, nk_strlen(text));
// (void)usr;
//}
//
//static void
//nk_bgfx_clipboard_copy(nk_handle usr, const char* text, int len)
//{
// char* str = 0;
// (void)usr;
// if (!len) return;
// str = (char*)malloc((size_t)len + 1);
// if (!str) return;
// memcpy(str, text, (size_t)len);
// str[len] = '\0';
// SDL_SetClipboardText(str);
// free(str);
//}
// extension to handle input grabs by nuklear
// https://github.com/vurtun/nuklear/issues/189
NK_API int
nk_input_is_mouse_click_in_rect_alt(const struct nk_input* i, enum nk_buttons id,
struct nk_rect b)
{
const struct nk_mouse_button* btn;
if (!i) return nk_false;
btn = &i->mouse.buttons[id];
return (nk_input_has_mouse_click_down_in_rect(i, id, b, nk_true) &&
btn->clicked) ? nk_true : nk_false;
}
NK_API int
nk_input_any_mouse_click_in_rect_alt(const struct nk_input* in, struct nk_rect b)
{
int i, down = 0;
for (i = 0; i < NK_BUTTON_MAX; ++i)
down = down || nk_input_is_mouse_click_in_rect_alt(in, (enum nk_buttons)i, b);
return down;
}
int nk_item_is_any_interacting(struct nk_context* ctx)
{
struct nk_window* iter;
NK_ASSERT(ctx);
if (!ctx) return 0;
iter = ctx->begin;
int r = 0;
while (iter) {
/* check if window is being hovered */
if (iter->flags & NK_WINDOW_MINIMIZED) {
struct nk_rect header = iter->bounds;
header.h = ctx->style.font->height + 2 * ctx->style.window.header.padding.y;
if (nk_input_any_mouse_click_in_rect_alt(&ctx->input, header)) {
r = r | NK_INTERACTING_MOUSE;
}
}
else if ((ctx->last_widget_state & NK_WIDGET_STATE_ACTIVE)
|| nk_input_any_mouse_click_in_rect_alt(&ctx->input, iter->bounds)
)
{
r = r | NK_INTERACTING_MOUSE;
}
/* check if window popup is being hovered */
// (Willing to accept hover blocking for popups for now)
if (iter->popup.active && iter->popup.win && nk_input_is_mouse_hovering_rect(&ctx->input, iter->popup.win->bounds))
r = r | NK_INTERACTING_MOUSE;
/* check if window edit is being edited */
if (iter->edit.active & NK_EDIT_ACTIVE)
r = r | NK_INTERACTING_KEYBOARD;
iter = iter->next;
}
return r;
}
#pragma once
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
// Defining this will zero out memory for each drawing command added to a drawing queue (inside nk_command_buffer_push). Zeroing command memory is very useful for fast checking (using memcmp) if command buffers are equal and avoid drawing frames when nothing on screen has changed since previous frame.
//#define NK_ZERO_COMMAND_MEMORY
// Defining this adds `stb_truetype` and `stb_rect_pack` implementation to this library and provides font baking and rendering. If you already have font handling or do not want to use this font handler you don't have to define it.
#define NK_INCLUDE_FONT_BAKING
// Defining this adds the default font: ProggyClean.ttf into this library which can be loaded into a font atlas and allows using this library without having a truetype font
#define NK_INCLUDE_DEFAULT_FONT
/// NK_INCLUDE_COMMAND_USERDATA | Defining this adds a userdata pointer into each command. Can be useful for example if you want to provide custom shaders depending on the used widget. Can be combined with the style structures.
/// NK_BUTTON_TRIGGER_ON_RELEASE | Different platforms require button clicks occurring either on buttons being pressed (up to down) or released (down to up). By default this library will react on buttons being pressed, but if you define this it will only trigger if a button is released.
/// NK_UINT_DRAW_INDEX | Defining this will set the size of vertex index elements when using NK_VERTEX_BUFFER_OUTPUT to 32bit instead of the default of 16bit
/// NK_KEYSTATE_BASED_INPUT | Define this if your backend uses key state for each frame rather than key press/release events
#include "nuklear/nuklear.h"
struct nk_bgfx {
nk_context ctx;
nk_font_atlas atlas;
nk_font* font = nullptr; // memory owned by atlas
nk_buffer cmds;
nk_draw_null_texture null;
bgfx::TextureHandle texture;
bgfx::ProgramHandle shader;
bgfx::UniformHandle s_texture;
bgfx::VertexDecl vertex_decl;
};
void nk_bgfx_init(nk_bgfx& nkb);
void nk_bgfx_destroy(nk_bgfx& nkb);
void nk_bgfx_render(nk_bgfx& nkb, int view_id, int width, int height);
#define NK_INTERACTING_MOUSE 1
#define NK_INTERACTING_KEYBOARD 2
int nk_item_is_any_interacting(struct nk_context* ctx);
$input v_texcoord0, v_color0
#include "bgfx_common.sci"
SAMPLER2D(s_texture, 0);
void main()
{
gl_FragColor = v_color0 * texture2D(s_texture, v_texcoord0);
}
$input a_position, a_texcoord0, a_color0
$output v_texcoord0, v_color0
#include "bgfx_common.sci"
void main()
{
v_texcoord0 = a_texcoord0;
v_color0 = a_color0;
gl_Position = mul(u_viewProj, vec4(a_position.xy, 0.0, 1.0));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment