colourpicker

Simple Colour Picker written in C
git clone anongit@rnpnr.xyz:colourpicker.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

Commit: bf184cf63d6ca2c6b92adfd411709a8501fc2ca5
Parent: 2417daf211d63787f3e46f0b1836e6e27f757d9b
Author: Randy Palamar
Date:   Sat, 10 Aug 2024 16:45:57 -0600

replace horrible MeasureTextEx and add debug cycle counts

This saves roughly ~9% of our CPU time.

Diffstat:
Mcolourpicker.c | 180++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mutil.c | 35+++++++++++++++++++++++++++++++++++
2 files changed, 162 insertions(+), 53 deletions(-)

diff --git a/colourpicker.c b/colourpicker.c @@ -5,9 +5,9 @@ #include "util.c" -static const char *mode_labels[CM_LAST][4] = { - [CM_RGB] = { "R", "G", "B", "A" }, - [CM_HSV] = { "H", "S", "V", "A" }, +static s8 mode_labels[CM_LAST][4] = { + [CM_RGB] = { s8("R"), s8("G"), s8("B"), s8("A") }, + [CM_HSV] = { s8("H"), s8("S"), s8("V"), s8("A") }, }; static void @@ -57,9 +57,25 @@ lerp_v4(v4 a, v4 b, f32 t) } static v2 -left_align_text_in_rect(Rect r, const char *text, Font font, f32 fontsize) +measure_text(Font font, s8 text) { - v2 ts = { .rv = MeasureTextEx(font, text, fontsize, 0) }; + v2 result = {.y = font.baseSize}; + + for (size i = 0; i < text.len; i++) { + /* NOTE: assumes font glyphs are ordered (they are in our embedded fonts) */ + i32 idx = (i32)text.data[i] - 32; + result.x += font.glyphs[idx].advanceX; + if (font.glyphs[idx].advanceX == 0) + result.x += (font.recs[idx].width + font.glyphs[idx].offsetX); + } + + return result; +} + +static v2 +left_align_text_in_rect(Rect r, s8 text, Font font, f32 fontsize) +{ + v2 ts = measure_text(font, text); v2 delta = { .h = r.size.h - ts.h }; return (v2) { .x = r.pos.x, .y = r.pos.y + 0.5 * delta.h, }; } @@ -76,9 +92,9 @@ scale_rect_centered(Rect r, v2 scale) } static v2 -center_align_text_in_rect(Rect r, const char *text, Font font, f32 fontsize) +center_align_text_in_rect(Rect r, s8 text, Font font) { - v2 ts = { .rv = MeasureTextEx(font, text, fontsize, 0) }; + v2 ts = measure_text(font, text); v2 delta = { .w = r.size.w - ts.w, .h = r.size.h - ts.h }; return (v2) { .x = r.pos.x + 0.5 * delta.w, @@ -290,7 +306,7 @@ set_text_input_idx(ColourPickerCtx *ctx, enum input_indices idx, Rect r, v2 mous static void do_text_input(ColourPickerCtx *ctx, Rect r, Color colour, i32 max_disp_chars) { - v2 ts = {.rv = MeasureTextEx(ctx->font, ctx->is.buf, ctx->font_size, 0)}; + v2 ts = measure_text(ctx->font, (s8){.len = ctx->is.buf_len, .data = ctx->is.buf}); v2 pos = {.x = r.pos.x, .y = r.pos.y + (r.size.y - ts.y) / 2}; i32 buf_delta = ctx->is.buf_len - max_disp_chars; @@ -298,7 +314,8 @@ do_text_input(ColourPickerCtx *ctx, Rect r, Color colour, i32 max_disp_chars) char *buf = ctx->is.buf + buf_delta; { /* NOTE: drop a char if the subtext still doesn't fit */ - v2 nts = {.rv = MeasureTextEx(ctx->font, buf, ctx->font_size, 0)}; + v2 nts = measure_text(ctx->font, (s8){.len = ctx->is.buf_len - buf_delta, + .data = buf}); if (nts.w > 0.96 * r.size.w) buf++; } @@ -323,27 +340,21 @@ do_text_input(ColourPickerCtx *ctx, Rect r, Color colour, i32 max_disp_chars) f32 x_off = TEXT_HALF_CHAR_WIDTH, x_bounds = r.size.w * ctx->is.cursor_hover_p; u32 i; for (i = 0; i < ctx->is.buf_len && x_off < x_bounds; i++) { - u32 idx = GetGlyphIndex(ctx->font, ctx->is.buf[i]); + /* NOTE: assumes font glyphs are ordered */ + i32 idx = ctx->is.buf[i] - 32; + x_off += ctx->font.glyphs[idx].advanceX; if (ctx->font.glyphs[idx].advanceX == 0) x_off += ctx->font.recs[idx].width; - else - x_off += ctx->font.glyphs[idx].advanceX; } ctx->is.cursor = i; } - /* NOTE: Braindead NULL termination stupidity */ - char saved_c = buf[ctx->is.cursor - buf_delta]; - buf[ctx->is.cursor - buf_delta] = 0; - - v2 sts = {.rv = MeasureTextEx(ctx->font, buf, ctx->font_size, 0)}; + v2 sts = measure_text(ctx->font, (s8){.len = ctx->is.cursor - buf_delta, .data = buf}); f32 cursor_x = r.pos.x + sts.x; f32 cursor_width; if (ctx->is.cursor == ctx->is.buf_len) cursor_width = MIN(ctx->window_size.w * 0.03, 20); else cursor_width = MIN(ctx->window_size.w * 0.01, 6); - buf[ctx->is.cursor - buf_delta] = saved_c; - Rect cursor_r = { .pos = {.x = cursor_x, .y = pos.y}, .size = {.w = cursor_width, .h = ts.h}, @@ -424,16 +435,16 @@ do_rect_button(ButtonState *btn, v2 mouse, Rect r, Color bg, f32 dt, f32 hover_s } static i32 -do_text_button(ColourPickerCtx *ctx, ButtonState *btn, v2 mouse, Rect r, char *text, v4 fg, Color bg) +do_text_button(ColourPickerCtx *ctx, ButtonState *btn, v2 mouse, Rect r, s8 text, v4 fg, Color bg) { i32 pressed_mask = do_rect_button(btn, mouse, r, bg, ctx->dt, TEXT_HOVER_SPEED, 1, 1); - v2 tpos = center_align_text_in_rect(r, text, ctx->font, ctx->font_size); + v2 tpos = center_align_text_in_rect(r, text, ctx->font); v2 spos = {.x = tpos.x + 1.75, .y = tpos.y + 2}; v4 colour = lerp_v4(fg, ctx->hover_colour, btn->hover_t); - DrawTextEx(ctx->font, text, spos.rv, ctx->font_size, 0, fade(BLACK, 0.8)); - DrawTextEx(ctx->font, text, tpos.rv, ctx->font_size, 0, colour_from_normalized(colour)); + DrawTextEx(ctx->font, text.data, spos.rv, ctx->font_size, 0, fade(BLACK, 0.8)); + DrawTextEx(ctx->font, text.data, tpos.rv, ctx->font_size, 0, colour_from_normalized(colour)); return pressed_mask; } @@ -506,17 +517,17 @@ do_slider(ColourPickerCtx *ctx, Rect r, i32 label_idx, v2 relative_mouse) set_text_input_idx(ctx, label_idx + 1, vr, relative_mouse); if (ctx->is.idx != (label_idx + 1)) { - const char *value = TextFormat("%0.02f", current); + s8 value = {.len = 4, .data = (char *)TextFormat("%0.02f", current)}; fpos = left_align_text_in_rect(vr, value, ctx->font, ctx->font_size); - DrawTextEx(ctx->font, value, fpos.rv, ctx->font_size, 0, colour_rl); + DrawTextEx(ctx->font, value.data, fpos.rv, ctx->font_size, 0, colour_rl); } else { do_text_input(ctx, vr, colour_rl, 4); } } - const char *label = mode_labels[ctx->colour_mode][label_idx]; - fpos = center_align_text_in_rect(lr, label, ctx->font, ctx->font_size); - DrawTextEx(ctx->font, label, fpos.rv, ctx->font_size, 0, ctx->fg); + s8 label = mode_labels[ctx->colour_mode][label_idx]; + fpos = center_align_text_in_rect(lr, label, ctx->font); + DrawTextEx(ctx->font, label.data, fpos.rv, ctx->font_size, 0, ctx->fg); } static void @@ -526,13 +537,14 @@ do_status_bar(ColourPickerCtx *ctx, Rect r, v2 relative_mouse) Rect mode_r; get_slider_subrects(r, 0, 0, &mode_r); - char *mode_txt; + s8 mode_txt = s8(""); switch (ctx->colour_mode) { - case CM_RGB: mode_txt = "RGB"; break; - case CM_HSV: mode_txt = "HSV"; break; - case CM_LAST: ASSERT(0); break; + case CM_RGB: mode_txt = s8("RGB"); break; + case CM_HSV: mode_txt = s8("HSV"); break; + case CM_LAST: ASSERT(0); break; } - v2 mode_ts = {.rv = MeasureTextEx(ctx->font, mode_txt, ctx->font_size, 0)}; + + v2 mode_ts = measure_text(ctx->font, mode_txt); mode_r.pos.y += (mode_r.size.h - mode_ts.h) / 2; mode_r.size.w = mode_ts.w; @@ -540,12 +552,12 @@ do_status_bar(ColourPickerCtx *ctx, Rect r, v2 relative_mouse) if (mouse_mask & MOUSE_LEFT) step_colour_mode(ctx, 1); if (mouse_mask & MOUSE_RIGHT) step_colour_mode(ctx, -1); - Color hc = colour_from_normalized(get_formatted_colour(ctx, CM_RGB)); - const char *hex = TextFormat("%02x%02x%02x%02x", hc.r, hc.g, hc.b, hc.a); - const char *label = "RGB: "; + Color hc = colour_from_normalized(get_formatted_colour(ctx, CM_RGB)); + s8 hex = {.len = 8, .data = (char *)TextFormat("%02x%02x%02x%02x", hc.r, hc.g, hc.b, hc.a)}; + s8 label = s8("RGB: "); - v2 label_size = {.rv = MeasureTextEx(ctx->font, label, ctx->font_size, 0)}; - v2 hex_size = {.rv = MeasureTextEx(ctx->font, hex, ctx->font_size, 0)}; + v2 label_size = measure_text(ctx->font, label); + v2 hex_size = measure_text(ctx->font, hex); f32 extra_input_scale = 1.07; hex_r.size.w = extra_input_scale * (label_size.w + hex_size.w); @@ -558,8 +570,8 @@ do_status_bar(ColourPickerCtx *ctx, Rect r, v2 relative_mouse) if (!hex_collides && ctx->is.idx == INPUT_HEX && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { set_text_input_idx(ctx, -1, hex_r, relative_mouse); - hc = colour_from_normalized(get_formatted_colour(ctx, CM_RGB)); - hex = TextFormat("%02x%02x%02x%02x", hc.r, hc.g, hc.b, hc.a); + hc = colour_from_normalized(get_formatted_colour(ctx, CM_RGB)); + hex.data = (char *)TextFormat("%02x%02x%02x%02x", hc.r, hc.g, hc.b, hc.a); } if (hex_collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) @@ -576,17 +588,17 @@ do_status_bar(ColourPickerCtx *ctx, Rect r, v2 relative_mouse) v4 mode_colour = lerp_v4(fg, ctx->hover_colour, ctx->sbs.mode.hover_t); v2 fpos = left_align_text_in_rect(label_r, label, ctx->font, ctx->font_size); - DrawTextEx(ctx->font, label, fpos.rv, ctx->font_size, 0, ctx->fg); + DrawTextEx(ctx->font, (char *)label.data, fpos.rv, ctx->font_size, 0, ctx->fg); Color hex_colour_rl = colour_from_normalized(hex_colour); if (ctx->is.idx != INPUT_HEX) { fpos = left_align_text_in_rect(hex_r, hex, ctx->font, ctx->font_size); - DrawTextEx(ctx->font, hex, fpos.rv, ctx->font_size, 0, hex_colour_rl); + DrawTextEx(ctx->font, hex.data, fpos.rv, ctx->font_size, 0, hex_colour_rl); } else { do_text_input(ctx, hex_r, hex_colour_rl, 8); } - DrawTextEx(ctx->font, mode_txt, mode_r.pos.rv, ctx->font_size, 0, + DrawTextEx(ctx->font, mode_txt.data, mode_r.pos.rv, ctx->font_size, 0, colour_from_normalized(mode_colour)); } @@ -672,8 +684,8 @@ do_colour_selector(ColourPickerCtx *ctx, Rect r) DrawRectangleRec(cs[0].rr, pcolour); DrawRectangleRec(cs[1].rr, colour); - v4 fg = normalize_colour(pack_rl_colour(ctx->fg)); - char *labels[2] = {"Revert", "Apply"}; + v4 fg = normalize_colour(pack_rl_colour(ctx->fg)); + s8 labels[2] = {s8("Revert"), s8("Apply")}; i32 pressed_idx = -1; for (i32 i = 0; i < ARRAY_COUNT(cs); i++) { @@ -690,12 +702,12 @@ do_colour_selector(ColourPickerCtx *ctx, Rect r) v4 colour = lerp_v4(fg, ctx->hover_colour, ctx->selection_hover_t[i]); - v2 fpos = center_align_text_in_rect(cs[i], labels[i], ctx->font, ctx->font_size); + v2 fpos = center_align_text_in_rect(cs[i], labels[i], ctx->font); v2 pos = fpos; pos.x += 1.75; pos.y += 2; - DrawTextEx(ctx->font, labels[i], pos.rv, ctx->font_size, 0, fade(BLACK, 0.8)); - DrawTextEx(ctx->font, labels[i], fpos.rv, ctx->font_size, 0, + DrawTextEx(ctx->font, labels[i].data, pos.rv, ctx->font_size, 0, fade(BLACK, 0.8)); + DrawTextEx(ctx->font, labels[i].data, fpos.rv, ctx->font_size, 0, colour_from_normalized(colour)); } @@ -740,6 +752,8 @@ do_slider_shader(ColourPickerCtx *ctx, Rect r, i32 colour_mode, f32 *regions, f3 static void do_slider_mode(ColourPickerCtx *ctx, v2 relative_mouse) { + BEGIN_CYCLE_COUNT(CC_DO_SLIDER); + Rect tr = { .size = { .w = ctx->picker_texture.texture.width, @@ -779,6 +793,8 @@ do_slider_mode(ColourPickerCtx *ctx, v2 relative_mouse) do_slider_shader(ctx, tr, ctx->colour_mode, regions, (f32 *)colours); EndTextureMode(); + + END_CYCLE_COUNT(CC_DO_SLIDER); } @@ -828,6 +844,8 @@ do_vertical_slider(ColourPickerCtx *ctx, v2 test_pos, Rect r, i32 idx, static void do_picker_mode(ColourPickerCtx *ctx, v2 relative_mouse) { + BEGIN_CYCLE_COUNT(CC_DO_PICKER); + v4 colour = get_formatted_colour(ctx, CM_HSV); colour.x = ctx->pms.base_hue + ctx->pms.fractional_hue; @@ -938,11 +956,61 @@ do_picker_mode(ColourPickerCtx *ctx, v2 relative_mouse) ctx->held_idx = -1; store_formatted_colour(ctx, colour, CM_HSV); + + END_CYCLE_COUNT(CC_DO_PICKER); +} + +static void +debug_dump_info(ColourPickerCtx *ctx) +{ +#ifdef _DEBUG + if (IsKeyPressed(KEY_F1)) + ctx->flags ^= CPF_PRINT_DEBUG; + + DrawFPS(20, 20); + + static char *fmts[CC_LAST] = { + [CC_WHOLE_RUN] = "Whole Run: %7ld cyc | %2d h | %7d cyc/h\n", + [CC_DO_PICKER] = "Picker Mode: %7ld cyc | %2d h | %7d cyc/h\n", + [CC_DO_SLIDER] = "Slider Mode: %7ld cyc | %2d h | %7d cyc/h\n", + [CC_UPPER] = "Upper: %7ld cyc | %2d h | %7d cyc/h\n", + [CC_LOWER] = "Lower: %7ld cyc | %2d h | %7d cyc/h\n", + [CC_TEMP] = "Temp: %7ld cyc | %2d h | %7d cyc/h\n", + }; + + i64 cycs[CC_LAST]; + i64 hits[CC_LAST]; + + for (u32 i = 0; i < CC_LAST; i++) { + cycs[i] = g_debug_clock_counts.total_cycles[i]; + hits[i] = g_debug_clock_counts.hit_count[i]; + g_debug_clock_counts.hit_count[i] = 0; + g_debug_clock_counts.total_cycles[i] = 0; + } + + if (!(ctx->flags & CPF_PRINT_DEBUG)) + return; + + static u32 fcount; + fcount++; + if (fcount != 60) + return; + fcount = 0; + + printf("Begin Cycle Dump\n"); + for (u32 i = 0; i < CC_LAST; i++) { + if (hits[i] == 0) + continue; + printf(fmts[i], cycs[i], hits[i], cycs[i]/hits[i]); + } +#endif } DEBUG_EXPORT void do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse_pos) { + BEGIN_CYCLE_COUNT(CC_WHOLE_RUN); + if (IsWindowResized()) { ctx->window_size.h = GetScreenHeight(); ctx->window_size.w = ctx->window_size.h / WINDOW_ASPECT_RATIO; @@ -987,6 +1055,8 @@ do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse .size = {.w = ws.w - 2 * pad.x, .h = ws.h * 0.4 - 1 * pad.y}, }; + BEGIN_CYCLE_COUNT(CC_UPPER); + Rect ma = cut_rect_left(upper, 0.84); Rect sa = cut_rect_right(upper, 0.84); do_colour_stack(ctx, sa); @@ -1049,7 +1119,10 @@ do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse DrawRectangleRec(ma.rr, fade(ctx->bg, 1 - ctx->mcs.mode_visible_t)); } + END_CYCLE_COUNT(CC_UPPER); + { + BEGIN_CYCLE_COUNT(CC_LOWER); Rect cb = lower; cb.size.h *= 0.25; cb.pos.y += 0.04 * lower.size.h; @@ -1124,11 +1197,11 @@ do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse Rect btn_r = mb; btn_r.size.h *= 0.46; - if (do_text_button(ctx, ctx->buttons + 0, ctx->mouse_pos, btn_r, "Copy", fg, bg)) + if (do_text_button(ctx, ctx->buttons + 0, ctx->mouse_pos, btn_r, s8("Copy"), fg, bg)) SetClipboardText(TextFormat("%02x%02x%02x%02x", bg.r, bg.g, bg.b, bg.a)); btn_r.pos.y += 0.54 * mb.size.h; - if (do_text_button(ctx, ctx->buttons + 1, ctx->mouse_pos, btn_r, "Paste", fg, bg)) { + if (do_text_button(ctx, ctx->buttons + 1, ctx->mouse_pos, btn_r, s8("Paste"), fg, bg)) { char *txt = (char *)GetClipboardText(); if (txt) { v4 new_colour = normalize_colour(parse_hex_u32(txt)); @@ -1140,10 +1213,11 @@ do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse } } } + + END_CYCLE_COUNT(CC_LOWER); } -#ifdef _DEBUG - DrawFPS(20, 20); -#endif + END_CYCLE_COUNT(CC_WHOLE_RUN); + debug_dump_info(ctx); } diff --git a/util.c b/util.c @@ -28,10 +28,14 @@ typedef uint8_t u8; typedef int32_t i32; typedef uint32_t u32; typedef uint32_t b32; +typedef int64_t i64; typedef float f32; typedef double f64; typedef ptrdiff_t size; +typedef struct { size len; char *data; } s8; +#define s8(s) (s8){.len = sizeof(s) - 1, .data = s} + typedef union { struct { u32 w, h; }; struct { u32 x, y; }; @@ -71,6 +75,7 @@ enum colour_picker_mode { enum colour_picker_flags { CPF_REFILL_TEXTURE = 1 << 0, + CPF_PRINT_DEBUG = 1 << 30, }; enum input_indices { @@ -155,6 +160,36 @@ typedef struct { char buf[64]; } InputState; +#ifdef _DEBUG +enum clock_counts { + CC_WHOLE_RUN, + CC_DO_PICKER, + CC_DO_SLIDER, + CC_UPPER, + CC_LOWER, + CC_TEMP, + CC_LAST +}; + +static struct { + i64 cpu_cycles[CC_LAST]; + i64 total_cycles[CC_LAST]; + i64 hit_count[CC_LAST]; +} g_debug_clock_counts; + +#define BEGIN_CYCLE_COUNT(cc_name) \ + g_debug_clock_counts.cpu_cycles[cc_name] = __rdtsc(); \ + g_debug_clock_counts.hit_count[cc_name]++ + +#define END_CYCLE_COUNT(cc_name) \ + g_debug_clock_counts.cpu_cycles[cc_name] = __rdtsc() - g_debug_clock_counts.cpu_cycles[cc_name]; \ + g_debug_clock_counts.total_cycles[cc_name] += g_debug_clock_counts.cpu_cycles[cc_name] + +#else +#define BEGIN_CYCLE_COUNT(a) +#define END_CYCLE_COUNT(a) +#endif + typedef struct { v4 colour, previous_colour; ColourStackState colour_stack;