Commit a374c931 authored by Niklas Haas's avatar Niklas Haas
Browse files

demos/nuklear: factor out nuklear implementation

This makes the UI code reusable, which will be useful for the changes to
plplay I have planned (basically exporting pl_render_params as nuklear
UI options).
parent bce4c79b
Pipeline #79226 passed with stages
in 7 minutes and 39 seconds
......@@ -16,8 +16,9 @@ endforeach
nuklear_inc = include_directories('./3rdparty/nuklear')
nuklear_found = cc.has_header('nuklear.h', include_directories: nuklear_inc)
nuklear = declare_dependency(
sources: 'ui.c',
include_directories: nuklear_inc,
compile_args: '-Wno-missing-prototypes',
compile_args: ['-Wno-missing-prototypes', '-DHAVE_UI'],
dependencies: libm,
)
......
/* Trivial nuklear demo, very unfinished. Currently just lets you set the
* background color of the program and nothing else.
/* Trivial UI demo, very unfinished. Currently just lets you change the
* background color of the window and nothing else.
*
* License: CC0 / Public Domain
*/
#include "common.h"
#include "window.h"
#include <libplacebo/dispatch.h>
#include <libplacebo/shaders/custom.h>
#define NK_IMPLEMENTATION
#define NK_PRIVATE
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_BUTTON_TRIGGER_ON_RELEASE
#include <nuklear.h>
#include "ui.h"
static struct pl_context *ctx;
static struct pl_dispatch *dp;
static struct window *win;
// UI state
static struct nk_context nk;
static struct nk_font_atlas atlas;
static struct nk_buffer cmds, verts, idx;
static const struct pl_tex *font_tex;
struct ui_vertex {
float pos[2];
float coord[2];
uint8_t color[4];
};
static struct pl_vertex_attrib vertex_attribs_pl[3] = {
{ .name = "pos", .offset = offsetof(struct ui_vertex, pos), },
{ .name = "coord", .offset = offsetof(struct ui_vertex, coord), },
{ .name = "vcolor", .offset = offsetof(struct ui_vertex, color), },
};
static const struct nk_draw_vertex_layout_element vertex_layout_nk[] = {
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, offsetof(struct ui_vertex, pos)},
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, offsetof(struct ui_vertex, coord)},
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, offsetof(struct ui_vertex, color)},
{NK_VERTEX_LAYOUT_END}
};
static struct nk_convert_config convert_cfg = {
.vertex_layout = vertex_layout_nk,
.vertex_size = sizeof(struct ui_vertex),
.vertex_alignment = NK_ALIGNOF(struct ui_vertex),
.shape_AA = NK_ANTI_ALIASING_ON,
.line_AA = NK_ANTI_ALIASING_ON,
.circle_segment_count = 22,
.curve_segment_count = 22,
.arc_segment_count = 22,
.global_alpha = 1.0f,
};
static bool ui_init()
{
const struct pl_gpu *gpu = win->gpu;
dp = pl_dispatch_create(ctx, gpu);
// Initialize font
nk_font_atlas_init_default(&atlas);
nk_font_atlas_begin(&atlas);
struct nk_font *font = nk_font_atlas_add_default(&atlas, 25, NULL);
struct pl_tex_params tparams = {
.format = pl_find_named_fmt(gpu, "r8"),
.sampleable = true,
.initial_data = nk_font_atlas_bake(&atlas, &tparams.w, &tparams.h,
NK_FONT_ATLAS_ALPHA8),
};
font_tex = pl_tex_create(gpu, &tparams);
nk_font_atlas_end(&atlas, nk_handle_ptr((void *) font_tex), &convert_cfg.null);
nk_font_atlas_cleanup(&atlas);
if (!font_tex)
return false;
// Initialize UI state
if (!nk_init_default(&nk, &font->handle)) {
fprintf(stderr, "NK: failed initializing UI!\n");
return false;
}
nk_buffer_init_default(&cmds);
nk_buffer_init_default(&verts);
nk_buffer_init_default(&idx);
// Pick vertex formats
vertex_attribs_pl[0].fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2);
vertex_attribs_pl[1].fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2);
vertex_attribs_pl[2].fmt = pl_find_named_fmt(gpu, "rgba8");
return true;
}
static struct ui *ui;
static bool render(const struct pl_swapchain_frame *frame)
{
const struct pl_gpu *gpu = win->gpu;
ui_update_input(ui, win);
// update input
int x, y;
window_get_cursor(win, &x, &y);
nk_input_begin(&nk);
nk_input_motion(&nk, x, y);
nk_input_button(&nk, NK_BUTTON_LEFT, x, y, window_get_button(win, BTN_LEFT));
nk_input_button(&nk, NK_BUTTON_RIGHT, x, y, window_get_button(win, BTN_RIGHT));
nk_input_button(&nk, NK_BUTTON_MIDDLE, x, y, window_get_button(win, BTN_MIDDLE));
nk_input_end(&nk);
// update UI
enum nk_panel_flags win_flags = NK_WINDOW_BORDER | NK_WINDOW_MOVABLE |
NK_WINDOW_SCALABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE;
static struct nk_colorf background = { 0.0f, 0.0f, 0.0f, 1.0f };
if (nk_begin(&nk, "Settings", nk_rect(100, 100, 500, 200), win_flags)) {
nk_layout_row_dynamic(&nk, 20, 1);
nk_label(&nk, "Window background:", NK_TEXT_LEFT);
nk_layout_row_dynamic(&nk, 25, 1);
if (nk_combo_begin_color(&nk, nk_rgb_cf(background), nk_vec2(nk_widget_width(&nk), 400))) {
nk_layout_row_dynamic(&nk, 120, 1);
nk_color_pick(&nk, &background, NK_RGB);
nk_combo_end(&nk);
struct nk_context *nk = ui_get_context(ui);
if (nk_begin(nk, "Settings", nk_rect(100, 100, 500, 200), win_flags)) {
nk_layout_row_dynamic(nk, 20, 1);
nk_label(nk, "Window background:", NK_TEXT_LEFT);
nk_layout_row_dynamic(nk, 25, 1);
if (nk_combo_begin_color(nk, nk_rgb_cf(background), nk_vec2(nk_widget_width(nk), 400))) {
nk_layout_row_dynamic(nk, 120, 1);
nk_color_pick(nk, &background, NK_RGB);
nk_combo_end(nk);
}
}
nk_end(&nk);
nk_end(nk);
assert(frame->fbo->params.blit_dst);
pl_tex_clear(gpu, frame->fbo, (const float *) &background.r);
// draw UI
if (nk_convert(&nk, &cmds, &verts, &idx, &convert_cfg) != NK_CONVERT_SUCCESS) {
fprintf(stderr, "NK: failed converting draw commands!\n");
return false;
}
const struct nk_draw_command *cmd = NULL;
const uint8_t *vertices = nk_buffer_memory(&verts);
const nk_draw_index *indices = nk_buffer_memory(&idx);
nk_draw_foreach(cmd, &nk, &cmds) {
if (!cmd->elem_count)
continue;
struct pl_shader *sh = pl_dispatch_begin(dp);
pl_shader_custom(sh, &(struct pl_custom_shader) {
.body = "color = texture(ui_tex, coord).r * vcolor;",
.output = PL_SHADER_SIG_COLOR,
.num_descriptors = 1,
.descriptors = &(struct pl_shader_desc) {
.desc = {
.name = "ui_tex",
.type = PL_DESC_SAMPLED_TEX,
},
.binding = {
.object = cmd->texture.ptr,
.sample_mode = PL_TEX_SAMPLE_LINEAR,
},
},
});
pl_tex_clear(win->gpu, frame->fbo, (const float *) &background.r);
bool ok = pl_dispatch_vertex(dp, &(struct pl_dispatch_vertex_params) {
.shader = &sh,
.target = frame->fbo,
.blend_params = &pl_alpha_overlay,
.scissors = {
.x0 = cmd->clip_rect.x,
.y0 = cmd->clip_rect.y,
.x1 = cmd->clip_rect.x + cmd->clip_rect.w,
.y1 = cmd->clip_rect.y + cmd->clip_rect.h,
},
.vertex_attribs = vertex_attribs_pl,
.num_vertex_attribs = sizeof(vertex_attribs_pl) / sizeof(vertex_attribs_pl[0]),
.vertex_stride = sizeof(struct ui_vertex),
.vertex_position_idx = 0,
.vertex_coords = PL_COORDS_ABSOLUTE,
.vertex_flipped = frame->flipped,
.vertex_type = PL_PRIM_TRIANGLE_LIST,
.vertex_count = cmd->elem_count,
.vertex_data = vertices,
.index_data = indices,
});
if (!ok) {
fprintf(stderr, "placebo: failed rendering UI!\n");
return false;
}
indices += cmd->elem_count;
}
nk_clear(&nk);
nk_buffer_clear(&cmds);
nk_buffer_clear(&verts);
nk_buffer_clear(&idx);
return true;
}
static void ui_uninit()
{
nk_buffer_free(&cmds);
nk_buffer_free(&verts);
nk_buffer_free(&idx);
nk_free(&nk);
nk_font_atlas_clear(&atlas);
pl_tex_destroy(win->gpu, &font_tex);
pl_dispatch_destroy(&dp);
return ui_draw(ui, frame);
}
static void uninit(int ret)
{
ui_uninit();
ui_destroy(&ui);
window_destroy(&win);
pl_context_destroy(&ctx);
exit(ret);
......@@ -225,7 +52,8 @@ int main(int argc, char **argv)
{
ctx = demo_context();
win = window_create(ctx, "nuklear demo", 640, 480, 0);
if (!win || !ui_init())
ui = win ? ui_create(win->gpu) : NULL;
if (!win || !ui)
uninit(1);
while (!win->window_lost) {
......
#define NK_IMPLEMENTATION
#include "ui.h"
#include <libplacebo/dispatch.h>
#include <libplacebo/shaders/custom.h>
struct ui_vertex {
float pos[2];
float coord[2];
uint8_t color[4];
};
#define NUM_VERTEX_ATTRIBS 3
struct ui {
const struct pl_gpu *gpu;
struct pl_dispatch *dp;
struct nk_context nk;
struct nk_font_atlas atlas;
struct nk_buffer cmds, verts, idx;
const struct pl_tex *font_tex;
struct pl_vertex_attrib attribs_pl[NUM_VERTEX_ATTRIBS];
struct nk_draw_vertex_layout_element attribs_nk[NUM_VERTEX_ATTRIBS+1];
struct nk_convert_config convert_cfg;
};
struct ui *ui_create(const struct pl_gpu *gpu)
{
struct ui *ui = malloc(sizeof(struct ui));
if (!ui)
return NULL;
*ui = (struct ui) {
.gpu = gpu,
.dp = pl_dispatch_create(gpu->ctx, gpu),
.attribs_pl = {
{
.name = "pos",
.offset = offsetof(struct ui_vertex, pos),
.fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
}, {
.name = "coord",
.offset = offsetof(struct ui_vertex, coord),
.fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
}, {
.name = "vcolor",
.offset = offsetof(struct ui_vertex, color),
.fmt = pl_find_named_fmt(gpu, "rgba8"),
}
},
.attribs_nk = {
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, offsetof(struct ui_vertex, pos)},
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, offsetof(struct ui_vertex, coord)},
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, offsetof(struct ui_vertex, color)},
{NK_VERTEX_LAYOUT_END}
},
.convert_cfg = {
.vertex_layout = ui->attribs_nk,
.vertex_size = sizeof(struct ui_vertex),
.vertex_alignment = NK_ALIGNOF(struct ui_vertex),
.shape_AA = NK_ANTI_ALIASING_ON,
.line_AA = NK_ANTI_ALIASING_ON,
.circle_segment_count = 22,
.curve_segment_count = 22,
.arc_segment_count = 22,
.global_alpha = 1.0f,
},
};
// Initialize font atlas using built-in font
nk_font_atlas_init_default(&ui->atlas);
nk_font_atlas_begin(&ui->atlas);
struct nk_font *font = nk_font_atlas_add_default(&ui->atlas, 25, NULL);
struct pl_tex_params tparams = {
.format = pl_find_named_fmt(gpu, "r8"),
.sampleable = true,
.initial_data = nk_font_atlas_bake(&ui->atlas, &tparams.w, &tparams.h,
NK_FONT_ATLAS_ALPHA8),
};
ui->font_tex = pl_tex_create(gpu, &tparams);
nk_font_atlas_end(&ui->atlas, nk_handle_ptr((void *) ui->font_tex),
&ui->convert_cfg.null);
nk_font_atlas_cleanup(&ui->atlas);
if (!ui->font_tex)
goto error;
// Initialize nuklear state
if (!nk_init_default(&ui->nk, &font->handle)) {
fprintf(stderr, "NK: failed initializing UI!\n");
goto error;
}
nk_buffer_init_default(&ui->cmds);
nk_buffer_init_default(&ui->verts);
nk_buffer_init_default(&ui->idx);
return ui;
error:
ui_destroy(&ui);
return NULL;
}
void ui_destroy(struct ui **ptr)
{
struct ui *ui = *ptr;
if (!ui)
return;
nk_buffer_free(&ui->cmds);
nk_buffer_free(&ui->verts);
nk_buffer_free(&ui->idx);
nk_free(&ui->nk);
nk_font_atlas_clear(&ui->atlas);
pl_tex_destroy(ui->gpu, &ui->font_tex);
pl_dispatch_destroy(&ui->dp);
free(ui);
*ptr = NULL;
}
void ui_update_input(struct ui *ui, const struct window *win)
{
int x, y;
window_get_cursor(win, &x, &y);
nk_input_begin(&ui->nk);
nk_input_motion(&ui->nk, x, y);
nk_input_button(&ui->nk, NK_BUTTON_LEFT, x, y, window_get_button(win, BTN_LEFT));
nk_input_button(&ui->nk, NK_BUTTON_RIGHT, x, y, window_get_button(win, BTN_RIGHT));
nk_input_button(&ui->nk, NK_BUTTON_MIDDLE, x, y, window_get_button(win, BTN_MIDDLE));
nk_input_end(&ui->nk);
}
struct nk_context *ui_get_context(struct ui *ui)
{
return &ui->nk;
}
bool ui_draw(struct ui *ui, const struct pl_swapchain_frame *frame)
{
if (nk_convert(&ui->nk, &ui->cmds, &ui->verts, &ui->idx, &ui->convert_cfg) != NK_CONVERT_SUCCESS) {
fprintf(stderr, "NK: failed converting draw commands!\n");
return false;
}
const struct nk_draw_command *cmd = NULL;
const uint8_t *vertices = nk_buffer_memory(&ui->verts);
const nk_draw_index *indices = nk_buffer_memory(&ui->idx);
nk_draw_foreach(cmd, &ui->nk, &ui->cmds) {
if (!cmd->elem_count)
continue;
struct pl_shader *sh = pl_dispatch_begin(ui->dp);
pl_shader_custom(sh, &(struct pl_custom_shader) {
.body = "color = texture(ui_tex, coord).r * vcolor;",
.output = PL_SHADER_SIG_COLOR,
.num_descriptors = 1,
.descriptors = &(struct pl_shader_desc) {
.desc = {
.name = "ui_tex",
.type = PL_DESC_SAMPLED_TEX,
},
.binding = {
.object = cmd->texture.ptr,
.sample_mode = PL_TEX_SAMPLE_NEAREST,
},
},
});
bool ok = pl_dispatch_vertex(ui->dp, &(struct pl_dispatch_vertex_params) {
.shader = &sh,
.target = frame->fbo,
.blend_params = &pl_alpha_overlay,
.scissors = {
.x0 = cmd->clip_rect.x,
.y0 = cmd->clip_rect.y,
.x1 = cmd->clip_rect.x + cmd->clip_rect.w,
.y1 = cmd->clip_rect.y + cmd->clip_rect.h,
},
.vertex_attribs = ui->attribs_pl,
.num_vertex_attribs = NUM_VERTEX_ATTRIBS,
.vertex_stride = sizeof(struct ui_vertex),
.vertex_position_idx = 0,
.vertex_coords = PL_COORDS_ABSOLUTE,
.vertex_flipped = frame->flipped,
.vertex_type = PL_PRIM_TRIANGLE_LIST,
.vertex_count = cmd->elem_count,
.vertex_data = vertices,
.index_data = indices,
});
if (!ok) {
fprintf(stderr, "placebo: failed rendering UI!\n");
return false;
}
indices += cmd->elem_count;
}
nk_clear(&ui->nk);
nk_buffer_clear(&ui->cmds);
nk_buffer_clear(&ui->verts);
nk_buffer_clear(&ui->idx);
return true;
}
#pragma once
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_BUTTON_TRIGGER_ON_RELEASE
#include <nuklear.h>
#include "common.h"
#include "window.h"
struct ui;
struct ui *ui_create(const struct pl_gpu *gpu);
void ui_destroy(struct ui **ui);
// Update/Logic/Draw cycle
void ui_update_input(struct ui *ui, const struct window *window);
struct nk_context *ui_get_context(struct ui *ui);
bool ui_draw(struct ui *ui, const struct pl_swapchain_frame *frame);
......@@ -29,5 +29,5 @@ enum button {
BTN_MIDDLE,
};
void window_get_cursor(struct window *window, int *x, int *y);
bool window_get_button(struct window *window, enum button);
void window_get_cursor(const struct window *window, int *x, int *y);
bool window_get_button(const struct window *window, enum button);
......@@ -228,7 +228,7 @@ void window_poll(struct window *window, bool block)
}
}
void window_get_cursor(struct window *window, int *x, int *y)
void window_get_cursor(const struct window *window, int *x, int *y)
{
struct priv *p = (struct priv *) window;
double dx, dy;
......@@ -237,7 +237,7 @@ void window_get_cursor(struct window *window, int *x, int *y)
*y = dy;
}
bool window_get_button(struct window *window, enum button btn)
bool window_get_button(const struct window *window, enum button btn)
{
static const int button_map[] = {
[BTN_LEFT] = GLFW_MOUSE_BUTTON_LEFT,
......
......@@ -223,12 +223,12 @@ void window_poll(struct window *window, bool block)
} while (ret);
}
void window_get_cursor(struct window *window, int *x, int *y)
void window_get_cursor(const struct window *window, int *x, int *y)
{
SDL_GetMouseState(x, y);
}
bool window_get_button(struct window *window, enum button btn)
bool window_get_button(const struct window *window, enum button btn)
{
static const uint32_t button_mask[] = {
[BTN_LEFT] = SDL_BUTTON_LMASK,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment