colourpicker

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

Commit: 265ce45e25db88a5382fc1e35a397af3ed213238
Parent: 5ed25eb34f0e6be9f3a1e7a44799eb4aa161a0b4
Author: Randy Palamar
Date:   Thu,  8 May 2025 09:18:59 -0600

replace simd conversions with simple version

All bulk conversions are done on the GPU so we can just do the
simple thing in the CPU code for the infrequent conversions.

Now the colourpicker can be built on non x86_64 platforms (DEBUG
only supported on aach64 and x86_64).

Diffstat:
Mcolourpicker.c | 30+++++++++++++++---------------
Mmain.c | 4++--
Mutil.c | 177++++++++++++++++++++++++++++++++++++-------------------------------------------
3 files changed, 98 insertions(+), 113 deletions(-)

diff --git a/colourpicker.c b/colourpicker.c @@ -304,7 +304,7 @@ set_text_input_idx(ColourPickerCtx *ctx, enum input_indices idx, Rect r, v2 mous Stream in = {.data = ctx->is.buf, .cap = ARRAY_COUNT(ctx->is.buf)}; if (idx == INPUT_HEX) { - stream_append_colour(&in, colour_from_normalized(get_formatted_colour(ctx, CM_RGB))); + stream_append_colour(&in, rl_colour_from_normalized(get_formatted_colour(ctx, CM_RGB))); } else { f32 fv = 0; switch (idx) { @@ -357,8 +357,8 @@ do_text_input(ColourPickerCtx *ctx, Rect r, Color colour, i32 max_disp_chars) v4 bg = ctx->cursor_colour; bg.a = 0; - Color cursor_colour = colour_from_normalized(lerp_v4(bg, ctx->cursor_colour, - ctx->is.cursor_t)); + Color cursor_colour = rl_colour_from_normalized(lerp_v4(bg, ctx->cursor_colour, + ctx->is.cursor_t)); /* NOTE: guess a cursor position */ if (ctx->is.cursor == -1) { @@ -478,7 +478,7 @@ do_text_button(ColourPickerCtx *ctx, ButtonState *btn, v2 mouse, Rect r, s8 text v4 colour = lerp_v4(fg, ctx->hover_colour, btn->hover_t); draw_text(ctx->font, text, spos, fade(BLACK, 0.8)); - draw_text(ctx->font, text, tpos, colour_from_normalized(colour)); + draw_text(ctx->font, text, tpos, rl_colour_from_normalized(colour)); return pressed_mask; } @@ -544,7 +544,7 @@ do_slider(ColourPickerCtx *ctx, Rect r, i32 label_idx, v2 relative_mouse) v4 colour = lerp_v4(normalize_colour(pack_rl_colour(ctx->fg)), ctx->hover_colour, s->colour_t[label_idx]); - Color colour_rl = colour_from_normalized(colour); + Color colour_rl = rl_colour_from_normalized(colour); if (collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) set_text_input_idx(ctx, label_idx + 1, vr, relative_mouse); @@ -588,7 +588,7 @@ do_status_bar(ColourPickerCtx *ctx, Rect r, v2 relative_mouse) u8 hbuf[8]; Stream hstream = {.data = hbuf, .cap = ARRAY_COUNT(hbuf)}; - stream_append_colour(&hstream, colour_from_normalized(get_formatted_colour(ctx, CM_RGB))); + stream_append_colour(&hstream, rl_colour_from_normalized(get_formatted_colour(ctx, CM_RGB))); s8 hex = {.len = hstream.widx, .data = hbuf}; s8 label = s8("RGB: "); @@ -607,7 +607,7 @@ do_status_bar(ColourPickerCtx *ctx, Rect r, v2 relative_mouse) IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { set_text_input_idx(ctx, -1, hex_r, relative_mouse); hstream.widx = 0; - stream_append_colour(&hstream, colour_from_normalized(get_formatted_colour(ctx, CM_RGB))); + stream_append_colour(&hstream, rl_colour_from_normalized(get_formatted_colour(ctx, CM_RGB))); hex.len = hstream.widx; } @@ -626,14 +626,14 @@ do_status_bar(ColourPickerCtx *ctx, Rect r, v2 relative_mouse) draw_text(ctx->font, label, left_align_text_in_rect(label_r, label, ctx->font), ctx->fg); - Color hex_colour_rl = colour_from_normalized(hex_colour); + Color hex_colour_rl = rl_colour_from_normalized(hex_colour); if (ctx->is.idx != INPUT_HEX) { draw_text(ctx->font, hex, left_align_text_in_rect(hex_r, hex, ctx->font), hex_colour_rl); } else { do_text_input(ctx, hex_r, hex_colour_rl, 8); } - draw_text(ctx->font, mode_txt, mode_r.pos, colour_from_normalized(mode_colour)); + draw_text(ctx->font, mode_txt, mode_r.pos, rl_colour_from_normalized(mode_colour)); } static void @@ -656,7 +656,7 @@ do_colour_stack(ColourPickerCtx *ctx, Rect sa) /* NOTE: Stack is moving up; draw last top item as it moves up and fades out */ if (css->fade_param) { ButtonState btn; - do_rect_button(&btn, ctx->mouse_pos, r, colour_from_normalized(css->last), ctx->dt, + do_rect_button(&btn, ctx->mouse_pos, r, rl_colour_from_normalized(css->last), ctx->dt, 0, 1, css->fade_param); r.pos.y += y_pos_delta; } @@ -666,7 +666,7 @@ do_colour_stack(ColourPickerCtx *ctx, Rect sa) f32 fade_scale[ARRAY_COUNT(css->items)] = { [ARRAY_COUNT(css->items) - 1] = 1 }; for (u32 i = 0; i < ARRAY_COUNT(css->items); i++) { i32 cidx = (css->widx + i) % ARRAY_COUNT(css->items); - Color bg = colour_from_normalized(css->items[cidx]); + Color bg = rl_colour_from_normalized(css->items[cidx]); b32 pressed = do_rect_button(css->buttons + cidx, ctx->mouse_pos, r, bg, ctx->dt, BUTTON_HOVER_SPEED, stack_scale_target, 1 - css->fade_param * fade_scale[i]); @@ -711,8 +711,8 @@ do_colour_stack(ColourPickerCtx *ctx, Rect sa) static void do_colour_selector(ColourPickerCtx *ctx, Rect r) { - Color colour = colour_from_normalized(get_formatted_colour(ctx, CM_RGB)); - Color pcolour = colour_from_normalized(ctx->previous_colour); + Color colour = rl_colour_from_normalized(get_formatted_colour(ctx, CM_RGB)); + Color pcolour = rl_colour_from_normalized(ctx->previous_colour); Rect cs[2] = {cut_rect_left(r, 0.5), cut_rect_right(r, 0.5)}; DrawRectangleRec(cs[0].rr, pcolour); @@ -741,7 +741,7 @@ do_colour_selector(ColourPickerCtx *ctx, Rect r) pos.x += 1.75; pos.y += 2; draw_text(ctx->font, labels[i], pos, fade(BLACK, 0.8)); - draw_text(ctx->font, labels[i], fpos, colour_from_normalized(colour)); + draw_text(ctx->font, labels[i], fpos, rl_colour_from_normalized(colour)); } DrawRectangleRoundedLinesEx(r.rr, SELECTOR_ROUNDNESS, 0, 4 * SELECTOR_BORDER_WIDTH, ctx->bg); @@ -1228,7 +1228,7 @@ do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse } v4 fg = normalize_colour(pack_rl_colour(ctx->fg)); - Color bg = colour_from_normalized(get_formatted_colour(ctx, CM_RGB)); + Color bg = rl_colour_from_normalized(get_formatted_colour(ctx, CM_RGB)); Rect btn_r = mb; btn_r.size.h *= 0.46; diff --git a/main.c b/main.c @@ -165,7 +165,7 @@ main(i32 argc, char *argv[]) BeginDrawing(); BeginTextureMode(icon_texture); ClearBackground(ctx.bg); - DrawCircleGradient(64, 64, 48, colour_from_normalized(hsv_to_rgb(ctx.colour)), ctx.bg); + DrawCircleGradient(64, 64, 48, rl_colour_from_normalized(hsv_to_rgb(ctx.colour)), ctx.bg); DrawRing((Vector2){64, 64}, 45, 48, 0, 360, 128, SELECTOR_BORDER_COLOUR); EndTextureMode(); EndDrawing(); @@ -193,7 +193,7 @@ main(i32 argc, char *argv[]) default: ASSERT(0); break; } - u32 packed_rgba = pack_rl_colour(colour_from_normalized(rgba)); + u32 packed_rgba = pack_rl_colour(rl_colour_from_normalized(rgba)); printf("0x%08X|{.r = %0.03f, .g = %0.03f, .b = %0.03f, .a = %0.03f}\n", packed_rgba, rgba.r, rgba.g, rgba.b, rgba.a); diff --git a/util.c b/util.c @@ -1,12 +1,23 @@ /* See LICENSE for copyright details */ #ifndef _UTIL_C_ #define _UTIL_C_ -#include <emmintrin.h> -#include <immintrin.h> - #include <stddef.h> #include <stdint.h> +typedef uint8_t u8; +typedef int32_t i32; +typedef uint32_t u32; +typedef uint32_t b32; +typedef int64_t i64; +typedef uint64_t u64; +typedef float f32; +typedef double f64; +typedef ptrdiff_t size; + +#define function static +#define global static +#define local_persist static + #include "shader_inc.h" #include "lora_sb_0_inc.h" #include "lora_sb_1_inc.h" @@ -16,24 +27,36 @@ #define asm __asm__ #endif +#define FORCE_INLINE inline __attribute__((always_inline)) + +#define fmod_f32(a, b) __builtin_fmodf((a), (b)) + +#ifdef __ARM_ARCH_ISA_A64 +#define debugbreak() asm volatile ("brk 0xf000") + +function FORCE_INLINE u64 +rdtsc(void) +{ + register u64 cntvct asm("x0"); + asm volatile ("mrs x0, cntvct_el0" : "=x"(cntvct)); + return cntvct; +} + +#elif __x86_64__ +#include <immintrin.h> +#define debugbreak() asm volatile ("int3; nop") + +#define rdtsc() __rdtsc() +#endif + #ifdef _DEBUG -#define ASSERT(c) do { if (!(c)) asm("int3; nop"); } while (0); +#define ASSERT(c) do { if (!(c)) debugbreak(); } while (0); #define DEBUG_EXPORT #else #define ASSERT(c) #define DEBUG_EXPORT static #endif -typedef uint8_t u8; -typedef int32_t i32; -typedef uint32_t u32; -typedef uint32_t b32; -typedef int64_t i64; -typedef uint64_t u64; -typedef float f32; -typedef double f64; -typedef ptrdiff_t size; - typedef struct { size len; u8 *data; } s8; #define s8(s) (s8){.len = sizeof(s) - 1, .data = (u8 *)s} @@ -187,11 +210,11 @@ static struct { } g_debug_clock_counts; #define BEGIN_CYCLE_COUNT(cc_name) \ - g_debug_clock_counts.cpu_cycles[cc_name] = __rdtsc(); \ + 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.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 @@ -240,110 +263,72 @@ typedef struct { #define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a)) #define ABS(x) ((x) < 0 ? (-x) : (x)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) #define CLAMP(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)) #define CLAMP01(a) CLAMP(a, 0, 1) #define ISDIGIT(a) ((a) >= '0' && (a) <= '9') #define ISHEX(a) (ISDIGIT(a) || ((a) >= 'a' && (a) <= 'f') || ((a) >= 'A' && (a) <= 'F')) -static Color -colour_from_normalized(v4 colour) +function Color +rl_colour_from_normalized(v4 colour) { - __m128 colour_v = _mm_loadu_ps(colour.E); - __m128 scale = _mm_set1_ps(255.0f); - __m128i result = _mm_cvtps_epi32(_mm_mul_ps(colour_v, scale)); - _Alignas(16) u32 outu[4]; - _mm_store_si128((__m128i *)outu, result); - return (Color){.r = outu[0] & 0xFF, .g = outu[1] & 0xFF, .b = outu[2] & 0xFF, .a = outu[3] & 0xFF }; + Color result; + result.r = colour.r * 255; + result.g = colour.g * 255; + result.b = colour.b * 255; + result.a = colour.a * 255; + return result; } -static v4 +function v4 rgb_to_hsv(v4 rgb) { - __m128 rgba = _mm_loadu_ps(rgb.E); - __m128 gbra = _mm_shuffle_ps(rgba, rgba, _MM_SHUFFLE(3, 0, 2, 1)); - __m128 brga = _mm_shuffle_ps(gbra, gbra, _MM_SHUFFLE(3, 0, 2, 1)); - - __m128 Max = _mm_max_ps(rgba, _mm_max_ps(gbra, brga)); - __m128 Min = _mm_min_ps(rgba, _mm_min_ps(gbra, brga)); - __m128 C = _mm_sub_ps(Max, Min); - - __m128 zero = _mm_set1_ps(0); - __m128 six = _mm_set1_ps(6); - - _Alignas(16) f32 aval[4] = {0, 2, 4, 0}; - _Alignas(16) f32 scale[4] = {1.0/6.0f, 0, 0, 0}; - /* NOTE if C == 0 then take H as 0/1 (which are equivalent in HSV) */ - __m128 t = _mm_div_ps(_mm_sub_ps(gbra, brga), C); - t = _mm_and_ps(t, _mm_cmpneq_ps(zero, C)); - t = _mm_add_ps(t, _mm_load_ps(aval)); - /* TODO: does (G - B) / C ever exceed 6.0? */ - /* NOTE: Compute fmodf on element [0] */ - t = _mm_sub_ps(t, _mm_mul_ps(_mm_floor_ps(_mm_mul_ps(t, _mm_load_ps(scale))), six)); - - __m128 H = _mm_div_ps(_mm_and_ps(t, _mm_cmpeq_ps(rgba, Max)), six); - __m128 S = _mm_div_ps(C, Max); - - /* NOTE: Make sure H & S are 0 instead of NaN when V == 0 */ - H = _mm_and_ps(H, _mm_cmpneq_ps(zero, Max)); - S = _mm_and_ps(S, _mm_cmpneq_ps(zero, Max)); - - __m128 H0 = _mm_shuffle_ps(H, H, _MM_SHUFFLE(3, 0, 0, 0)); - __m128 H1 = _mm_shuffle_ps(H, H, _MM_SHUFFLE(3, 1, 1, 1)); - __m128 H2 = _mm_shuffle_ps(H, H, _MM_SHUFFLE(3, 2, 2, 2)); - H = _mm_or_ps(H0, _mm_or_ps(H1, H2)); - - /* NOTE: keep only element [0] from H vector; Max contains V & A */ - __m128 hva = _mm_blend_ps(Max, H, 0x01); - __m128 hsva = _mm_blend_ps(hva, S, 0x02); - hsva = _mm_min_ps(hsva, _mm_set1_ps(1)); - - v4 res; - _mm_storeu_ps(res.E, hsva); - return res; + v4 hsv = {0}; + f32 M = MAX(rgb.r, MAX(rgb.g, rgb.b)); + f32 m = MIN(rgb.r, MIN(rgb.g, rgb.b)); + if (M - m > 0) { + f32 C = M - m; + if (M == rgb.r) { + hsv.x = fmod_f32((rgb.g - rgb.b) / C, 6) / 6.0; + } else if (M == rgb.g) { + hsv.x = ((rgb.b - rgb.r) / C + 2) / 6.0; + } else { + hsv.x = ((rgb.r - rgb.g) / C + 4) / 6.0; + } + hsv.y = M? C / M : 0; + hsv.z = M; + hsv.a = rgb.a; + } + return hsv; } -static v4 +function v4 hsv_to_rgb(v4 hsv) { - /* f(k(n)) = V - V*S*max(0, min(k, min(4 - k, 1))) - * k(n) = fmod((n + H * 6), 6) - * (R, G, B) = (f(n = 5), f(n = 3), f(n = 1)) - */ - _Alignas(16) f32 nval[4] = {5.0f, 3.0f, 1.0f, 0.0f}; - __m128 n = _mm_load_ps(nval); - __m128 H = _mm_set1_ps(hsv.x); - __m128 S = _mm_set1_ps(hsv.y); - __m128 V = _mm_set1_ps(hsv.z); - __m128 six = _mm_set1_ps(6); - - __m128 t = _mm_add_ps(n, _mm_mul_ps(six, H)); - __m128 rem = _mm_floor_ps(_mm_div_ps(t, six)); - __m128 k = _mm_sub_ps(t, _mm_mul_ps(rem, six)); - - t = _mm_min_ps(_mm_sub_ps(_mm_set1_ps(4), k), _mm_set1_ps(1)); - t = _mm_max_ps(_mm_set1_ps(0), _mm_min_ps(k, t)); - t = _mm_mul_ps(t, _mm_mul_ps(S, V)); - v4 rgba; - _mm_storeu_ps(rgba.E, _mm_sub_ps(V, t)); + f32 k = fmod_f32(5 + hsv.x * 6, 6); + rgba.r = hsv.z - hsv.z * hsv.y * MAX(0, MIN(1, MIN(k, 4 - k))); + k = fmod_f32(3 + hsv.x * 6, 6); + rgba.g = hsv.z - hsv.z * hsv.y * MAX(0, MIN(1, MIN(k, 4 - k))); + k = fmod_f32(1 + hsv.x * 6, 6); + rgba.b = hsv.z - hsv.z * hsv.y * MAX(0, MIN(1, MIN(k, 4 - k))); rgba.a = hsv.a; - return rgba; } -static v4 +function v4 normalize_colour(u32 rgba) { - return (v4){ - .r = ((rgba >> 24) & 0xFF) / 255.0f, - .g = ((rgba >> 16) & 0xFF) / 255.0f, - .b = ((rgba >> 8) & 0xFF) / 255.0f, - .a = ((rgba >> 0) & 0xFF) / 255.0f, - }; + v4 result; + result.r = ((rgba >> 24) & 0xFF) / 255.0f; + result.g = ((rgba >> 16) & 0xFF) / 255.0f; + result.b = ((rgba >> 8) & 0xFF) / 255.0f; + result.a = ((rgba >> 0) & 0xFF) / 255.0f; + return result; } -static u32 +function u32 pack_rl_colour(Color colour) { return colour.r << 24 | colour.g << 16 | colour.b << 8 | colour.a << 0;