From 8a4b4bed8259397c6d958766573a0bfa25909b98 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Fri, 25 Apr 2025 13:12:18 +0200 Subject: Cleanup --- graphics.c | 2544 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 1280 insertions(+), 1264 deletions(-) (limited to 'graphics.c') diff --git a/graphics.c b/graphics.c index 7a97aab..5632712 100644 --- a/graphics.c +++ b/graphics.c @@ -239,284 +239,339 @@ void clean_graphics_requests_cache(i64 amount); #endif // GRAPHICS_HEADER_GUARD_ +// ================================================================ +// +// IMPLEMENTATION +// // ================================================================ #ifndef GRAPHICS_HEADER #ifndef GRAPHICS_IMPL_GUARD_ #define GRAPHICS_IMPL_GUARD_ -static void put_pixel_(vec4_f32 *dst, vec4_f32 color) { - if (color.w == 1.f) - *dst = color; - else { - f32 dst_amount = 1. - color.w; +// ================================================================ +// +// Colors +// +// ================================================================ - *dst = (vec4_f32) { - .x = dst->x * dst_amount + color.x * color.w, - .y = dst->y * dst_amount + color.y * color.w, - .z = dst->z * dst_amount + color.z * color.w, - .w = max2_f32_(dst->w, color.w), - }; - } +vec3_f32 vec3_from_vec4_f32(vec4_f32 v) { + return (vec3_f32) { + .x = v.x, + .y = v.y, + .z = v.z, + }; } -void draw_pixels_to_buffer(Pixel_Buffer dst, Box area, Pixel_Buffer src) { - if (area.width < EPSILON || area.height < EPSILON) - return; - - PROFILER_begin(PROFILE_DRAW_PIXELS); - - i64 i0 = (i64) floor(area.x + 0.5); - i64 i1 = (i64) floor(area.x + area.width + 0.5); - i64 j0 = (i64) floor(area.y + 0.5); - i64 j1 = (i64) floor(area.y + area.height + 0.5); +vec4_f32 vec4_from_vec3_f32(vec3_f32 v, f32 w) { + return (vec4_f32) { + .x = v.x, + .y = v.y, + .z = v.z, + .w = w, + }; +} - if (i0 < 0) i0 = 0; - if (i1 > dst.width) i1 = dst.width; - if (j0 < 0) j0 = 0; - if (j1 > dst.height) j1 = dst.height; +vec3_f32 vec3_f32_lerp(vec3_f32 a, vec3_f32 b, f32 t) { + return (vec3_f32) { + .x = a.x + (b.x - a.x) * t, + .y = a.y + (b.y - a.y) * t, + .z = a.z + (b.z - a.z) * t, + }; +} - // FIXME, PERF: Use integer arithmetic. +vec4_f32 vec4_f32_lerp(vec4_f32 a, vec4_f32 b, f32 t) { + return (vec4_f32) { + .x = a.x + (b.x - a.x) * t, + .y = a.y + (b.y - a.y) * t, + .z = a.z + (b.z - a.z) * t, + .w = a.w + (b.w - a.w) * t, + }; +} - f64 di = src.width / area.width; - f64 dj = src.height / area.height; - f64 jj = (j0 - area.y) * dj + dj * .5; - for (i64 j = j0; j < j1; ++j, jj += dj) { - if (jj < 0 || jj >= src.height) continue; - vec4_f32 *d = dst.pixels + j * dst.stride + i0; - vec4_f32 *d_end = d + i1 - i0; - vec4_f32 *s = src.pixels + (i64) jj * src.stride; - f64 ii = (i0 - area.x) * di + di * .5; - if (ii < 0 || ii >= src.width) continue; - for (; d < d_end; ++d, ii += di) - put_pixel_(d, s[(i64) ii]); - } +vec3_f32 rgb_gamma_add(vec3_f32 rgb) { + return (vec3_f32) { + .x = gamma_(rgb.x), + .y = gamma_(rgb.y), + .z = gamma_(rgb.z), + }; +} - PROFILER_end(PROFILE_DRAW_PIXELS); +vec3_f32 rgb_gamma_remove(vec3_f32 rgb) { + return (vec3_f32) { + .x = gamma_re_(rgb.x), + .y = gamma_re_(rgb.y), + .z = gamma_re_(rgb.z), + }; } -void fill_rectangle_to_buffer(Pixel_Buffer dst, vec4_f32 color, Box area) { - PROFILER_begin(PROFILE_FILL_RECTANGLE); +vec4_f32 rgba_gamma_add(vec4_f32 rgba) { + return (vec4_f32) { + .x = gamma_(rgba.x), + .y = gamma_(rgba.y), + .z = gamma_(rgba.z), + .w = gamma_(rgba.w), + }; +} - i64 i0 = (i64) floor(area.x + 0.5); - i64 i1 = (i64) floor(area.x + area.width + 0.5); - i64 j0 = (i64) floor(area.y + 0.5); - i64 j1 = (i64) floor(area.y + area.height + 0.5); +vec4_f32 rgba_gamma_remove(vec4_f32 rgba) { + return (vec4_f32) { + .x = gamma_re_(rgba.x), + .y = gamma_re_(rgba.y), + .z = gamma_re_(rgba.z), + .w = gamma_re_(rgba.w), + }; +} - if (i0 < 0) i0 = 0; - if (i1 > dst.width) i1 = dst.width; - if (j0 < 0) j0 = 0; - if (j1 > dst.height) j1 = dst.height; +vec3_f32 lab_from_rgb(vec3_f32 rgb) { + f64 l = 0.4122214708 * rgb.x + 0.5363325363 * rgb.y + 0.0514459929 * rgb.z; + f64 m = 0.2119034982 * rgb.x + 0.6806995451 * rgb.y + 0.1073969566 * rgb.z; + f64 s = 0.0883024619 * rgb.x + 0.2817188376 * rgb.y + 0.6299787005 * rgb.z; - for (i64 j = j0; j < j1; ++j) { - vec4_f32 *p = dst.pixels + j * dst.stride + i0; - vec4_f32 *p_end = dst.pixels + j * dst.stride + i1; - for (; p < p_end; ++p) - put_pixel_(p, color); - } + f64 l_ = cbrt(l); + f64 m_ = cbrt(m); + f64 s_ = cbrt(s); - PROFILER_end(PROFILE_FILL_RECTANGLE); + return (vec3_f32) { + .x = (f32) (0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_), + .y = (f32) (1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_), + .z = (f32) (0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_), + }; } -void fill_triangle_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[3]) { - PROFILER_begin(PROFILE_FILL_TRIANGLE); - - f64 x0 = vertices[0].x; - f64 y0 = vertices[0].y; - f64 x1 = vertices[1].x; - f64 y1 = vertices[1].y; - f64 x2 = vertices[2].x; - f64 y2 = vertices[2].y; +vec3_f32 rgb_from_lab(vec3_f32 lab) { + f64 l_ = lab.x + 0.3963377774 * lab.y + 0.2158037573 * lab.z; + f64 m_ = lab.x - 0.1055613458 * lab.y - 0.0638541728 * lab.z; + f64 s_ = lab.x - 0.0894841775 * lab.y - 1.2914855480 * lab.z; - i64 i0 = (i64) floor(min3_(x0, x1, x2)); - i64 j0 = (i64) floor(min3_(y0, y1, y2)); - i64 i1 = (i64) ceil (max3_(x0, x1, x2)); - i64 j1 = (i64) ceil (max3_(y0, y1, y2)); + f64 l = l_ * l_ * l_; + f64 m = m_ * m_ * m_; + f64 s = s_ * s_ * s_; - if (i0 < 0) i0 = 0; - if (i1 >= dst.width) i1 = dst.width - 1; - if (j0 < 0) j0 = 0; - if (j1 >= dst.height) j1 = dst.height - 1; + return (vec3_f32) { + .x = (f32) (+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s), + .y = (f32) (-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s), + .z = (f32) (-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s), + }; +} - for (i64 j = j0; j <= j1; ++j) { - // FIXME, PERF: Calculate the exact intersection. +vec3_f32 lch_from_lab(vec3_f32 lab) { + f64 a = lab.y; + f64 b = lab.z; - i64 left = i1; - i64 right = i0; + return (vec3_f32) { + .x = lab.x, + .y = (f32) sqrt (a * a + b * b), + .z = (f32) atan2(b, a), + }; +} - for (i64 i = i0; i <= i1; ++i) - if (hit_triangle(vertices, (vec2) { (f64) i, (f64) j, })) { - left = i; - break; - } +vec3_f32 lab_from_lch(vec3_f32 lch) { + return (vec3_f32) { + .x = lch.x, + .y = (f32) (lch.y * cos(lch.z)), + .z = (f32) (lch.y * sin(lch.z)), + }; +} - for (i64 i = i1; i >= i0; --i) - if (hit_triangle(vertices, (vec2) { (f64) i, (f64) j, })) { - right = i; - break; - } +vec3_f32 rgb_from_lch(vec3_f32 lch) { + return rgb_from_lab(lab_from_lch(lch)); +} - vec4_f32 *p = dst.pixels + j * dst.stride + left; - vec4_f32 *p_end = dst.pixels + j * dst.stride + right + 1; - for (; p < p_end; ++p) - put_pixel_(p, color); - } +vec3_f32 lch_from_rgb(vec3_f32 rgb) { + return lch_from_lab(lab_from_rgb(rgb)); +} - PROFILER_end(PROFILE_FILL_TRIANGLE); +vec3_f32 rgb_gamma_lerp (vec3_f32 a, vec3_f32 b, f32 t) { + return rgb_gamma_add(vec3_f32_lerp( + rgb_gamma_remove(a), + rgb_gamma_remove(b), + t + )); } -static void triangles_area_(f64 *x0, f64 *y0, f64 *x1, f64 *y1, vec2 p, vec2 s, i64 num, vec2 *v) { - if (num <= 0) - return; +vec4_f32 rgba_gamma_lerp(vec4_f32 a, vec4_f32 b, f32 t) { + return rgba_gamma_add(vec4_f32_lerp( + rgba_gamma_remove(a), + rgba_gamma_remove(b), + t + )); +} - *x0 = min3_(p.x + v[0].x * s.x, p.x + v[1].x * s.x, p.x + v[2].x * s.x); - *y0 = min3_(p.y + v[0].y * s.y, p.y + v[1].y * s.y, p.y + v[2].y * s.y); - *x1 = max3_(p.x + v[0].x * s.x, p.x + v[1].x * s.x, p.x + v[2].x * s.x); - *y1 = max3_(p.y + v[0].y * s.y, p.y + v[1].y * s.y, p.y + v[2].y * s.y); +vec3_f32 rgb_mix(vec3_f32 a, vec3_f32 b, f32 t) { + vec3_f32 a_lch = lch_from_rgb(a); + vec3_f32 b_lch = lch_from_rgb(b); - for (i64 i = 1; i < num; ++i) { - *x0 = min4_(*x0, p.x + v[i * 3].x * s.x, p.x + v[i * 3 + 1].x * s.x, p.x + v[i * 3 + 2].x * s.x); - *y0 = min4_(*y0, p.y + v[i * 3].y * s.y, p.y + v[i * 3 + 1].y * s.y, p.y + v[i * 3 + 2].y * s.y); - *x1 = max4_(*x1, p.x + v[i * 3].x * s.x, p.x + v[i * 3 + 1].x * s.x, p.x + v[i * 3 + 2].x * s.x); - *y1 = max4_(*y1, p.y + v[i * 3].y * s.y, p.y + v[i * 3 + 1].y * s.y, p.y + v[i * 3 + 2].y * s.y); - } + return rgb_from_lch((vec3_f32) { + a_lch.x + (b_lch.x - a_lch.x) * t, + a_lch.y + (b_lch.y - a_lch.y) * t, + lch_from_rgb(rgb_gamma_lerp(a, b, t)).z, + }); } -void fill_triangles_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices) { - PROFILER_begin(PROFILE_FILL_TRIANGLES); +vec4_f32 rgba_mix(vec4_f32 a, vec4_f32 b, f32 t) { + vec3_f32 a_lch = lch_from_rgb(vec3_from_vec4_f32(a)); + vec3_f32 b_lch = lch_from_rgb(vec3_from_vec4_f32(b)); + vec4_f32 ab = rgba_gamma_lerp(a, b, t); - f64 x0, y0, x1, y1; - triangles_area_(&x0, &y0, &x1, &y1, position, scale, num_triangles, vertices); + return vec4_from_vec3_f32(rgb_from_lch((vec3_f32) { + a_lch.x + (b_lch.x - a_lch.x) * t, + a_lch.y + (b_lch.y - a_lch.y) * t, + lch_from_rgb(vec3_from_vec4_f32(ab)).z, + }), ab.w); +} - i64 i0 = (i64) floor(x0); - i64 j0 = (i64) floor(y0); - i64 i1 = (i64) ceil (x1); - i64 j1 = (i64) ceil (y1); +vec3_f32 lch_mix(vec3_f32 a, vec3_f32 b, f32 t) { + return (vec3_f32) { + a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + lch_from_rgb(rgb_gamma_lerp(rgb_from_lch(a), rgb_from_lch(b), t)).z, + }; +} - if (i0 < 0) i0 = 0; - if (i1 >= dst.width) i1 = dst.width - 1; - if (j0 < 0) j0 = 0; - if (j1 >= dst.height) j1 = dst.height - 1; +vec3_f32 lch_lerp(vec3_f32 a, vec3_f32 b, f32 t) { + f32 delta_hue = b.z - a.z; + if (delta_hue > M_PI) delta_hue -= M_PI * 2; + if (delta_hue < -M_PI) delta_hue += M_PI * 2; - // FIXME, PERF: Use stencil buffer. + return (vec3_f32) { + .x = a.x + (b.x - a.x) * t, + .y = a.y + (b.y - a.y) * t, + .z = a.z + delta_hue * t, + }; +} - for (i64 j = j0; j <= j1; ++j) - for (i64 i = i0; i <= i1; ++i) - for (i64 k = 0; k < num_triangles; ++k) { - vec2 triangle[3] = { - vertices[3 * k], - vertices[3 * k + 1], - vertices[3 * k + 2], - }; +u32 rgb_u32_from_f32(vec3_f32 color) { + i32 ir = (i32) floor(color.x * 255. + .5); + i32 ig = (i32) floor(color.y * 255. + .5); + i32 ib = (i32) floor(color.z * 255. + .5); - for (i64 r = 0; r < 3; ++r) { - triangle[r].x = position.x + triangle[r].x * scale. x; - triangle[r].y = position.y + triangle[r].y * scale.y; - } + u32 r = ir < 0 ? 0u : ir > 255 ? 255u : (u32) ir; + u32 g = ig < 0 ? 0u : ig > 255 ? 255u : (u32) ig; + u32 b = ib < 0 ? 0u : ib > 255 ? 255u : (u32) ib; - if (hit_triangle(triangle, (vec2) { (f64) i, (f64) j, })) { - put_pixel_(dst.pixels + j * dst.stride + i, color); - break; - } - } + return (r << 16) | (g << 8) | b; +} - PROFILER_end(PROFILE_FILL_TRIANGLES); +vec3_f32 rgb_f32_from_u32(u32 color) { + return (vec3_f32) { + .x = ((color & 0xff0000) >> 16) / 255.f, + .y = ((color & 0x00ff00) >> 8) / 255.f, + .z = (color & 0x0000ff) / 255.f, + }; } -void fill_quad_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[4]) { - PROFILER_begin(PROFILE_FILL_QUAD); +u32 rgba_u32_from_f32(vec4_f32 color) { + i32 ir = (i32) floor(color.x * 255. + .5); + i32 ig = (i32) floor(color.y * 255. + .5); + i32 ib = (i32) floor(color.z * 255. + .5); + i32 ia = (i32) floor(color.w * 255. + .5); + + u32 r = ir < 0 ? 0u : ir > 255 ? 255u : (u32) ir; + u32 g = ig < 0 ? 0u : ig > 255 ? 255u : (u32) ig; + u32 b = ib < 0 ? 0u : ib > 255 ? 255u : (u32) ib; + u32 a = ia < 0 ? 0u : ia > 255 ? 255u : (u32) ia; + + return (a << 24) | (r << 16) | (g << 8) | b; +} + +vec4_f32 rgba_f32_from_u32(u32 color) { + return (vec4_f32) { + .x = ((color & 0x00ff0000) >> 16) / 255.f, + .y = ((color & 0x0000ff00) >> 8) / 255.f, + .z = (color & 0x000000ff) / 255.f, + .w = ((color & 0xff000000) >> 24) / 255.f, + }; +} + +// ================================================================ +// +// Basic shapes rendering +// +// ================================================================ + +b8 hit_rectangle(Box area, vec2 point) { + return point.x >= area.x && point.x < area.x + area.width && point.y >= area.y && point.y < area.y + area.height; +} + +static b8 same_sign_(f64 a, f64 b) { + if (a >= EPSILON && b <= -EPSILON) return 0; + if (a <= -EPSILON && b >= EPSILON) return 0; + return 1; +} +b8 hit_triangle(vec2 vertices[3], vec2 point) { f64 x0 = vertices[0].x; f64 y0 = vertices[0].y; f64 x1 = vertices[1].x; f64 y1 = vertices[1].y; f64 x2 = vertices[2].x; f64 y2 = vertices[2].y; - f64 x3 = vertices[3].x; - f64 y3 = vertices[3].y; - - i64 i0 = (i64) floor(min4_(x0, x1, x2, x3)); - i64 j0 = (i64) floor(min4_(y0, y1, y2, y3)); - i64 i1 = (i64) ceil (max4_(x0, x1, x2, x3)); - i64 j1 = (i64) ceil (max4_(y0, y1, y2, y3)); + f64 px = point.x; + f64 py = point.y; - if (i0 < 0) i0 = 0; - if (i1 >= dst.width) i1 = dst.width - 1; - if (j0 < 0) j0 = 0; - if (j1 >= dst.height) j1 = dst.height - 1; + if ( (px < x0 && px < x1 && px < x2) + || (px > x0 && px > x1 && px > x2) + || (py < y0 && py < y1 && py < y2) + || (py > y0 && py > y1 && py > y2) ) + return 0; - for (i64 j = j0; j <= j1; ++j) { - // FIXME, PERF: Calculate the exact intersection. + f64 x10 = x1 - x0; + f64 x21 = x2 - x1; + f64 x02 = x0 - x2; - i64 left = i1; - i64 right = i0; + f64 y10 = y1 - y0; + f64 y21 = y2 - y1; + f64 y02 = y0 - y2; - for (i64 i = i0; i <= i1; ++i) - if (hit_quad(vertices, (vec2) { (f64) i, (f64) j, })) { - left = i; - break; - } + // Z-components of cross-products - for (i64 i = i1; i >= i0; --i) - if (hit_quad(vertices, (vec2) { (f64) i, (f64) j, })) { - right = i; - break; - } + f64 z0 = x02 * y10 - x10 * y02; + f64 z1 = x10 * y21 - x21 * y10; + f64 z2 = x21 * y02 - x02 * y21; - vec4_f32 *p = dst.pixels + j * dst.stride + left; - vec4_f32 *p_end = dst.pixels + j * dst.stride + right + 1; - for (; p < p_end; ++p) - put_pixel_(p, color); - } + f64 pz0 = x02 * (py - y0) - (px - x0) * y02; + f64 pz1 = x10 * (py - y1) - (px - x1) * y10; + f64 pz2 = x21 * (py - y2) - (px - x2) * y21; - PROFILER_end(PROFILE_FILL_QUAD); + return same_sign_(z0, pz0) + && same_sign_(z1, pz1) + && same_sign_(z2, pz2); } -void fill_ellipse_to_buffer(Pixel_Buffer dst, vec4_f32 color, Box area) { - PROFILER_begin(PROFILE_FILL_ELLIPSE); - - f64 y1 = area.y + area.height; - - i64 j0 = (i64) floor(area.y); - i64 j1 = (i64) ceil (y1); - - if (j0 < 0) j0 = 0; - if (j1 >= dst.height) j1 = dst.height - 1; - - f64 half_w = area.width / 2.0; - f64 half_h = area.height / 2.0; - - if (half_h < EPSILON) - goto _finish; - f64 inv_half_h = 1.0 / half_h; - - f64 cx = area.x + half_w; - f64 cy = area.y + half_h; +b8 hit_quad(vec2 vertices[4], vec2 point) { + f64 x0 = vertices[0].x; + f64 y0 = vertices[0].y; + f64 x1 = vertices[1].x; + f64 y1 = vertices[1].y; + f64 x2 = vertices[2].x; + f64 y2 = vertices[2].y; + f64 x3 = vertices[3].x; + f64 y3 = vertices[3].y; - for (i64 j = j0; j <= j1; ++j) { - f64 ry = (j - cy + 0.5) * inv_half_h; - if (ry > 1.0 - EPSILON || ry < -1.0 + EPSILON) - continue; + return hit_triangle((vec2[3]) { { x0, y0 }, { x1, y1 }, { x2, y2 }, }, point) + || hit_triangle((vec2[3]) { { x0, y0 }, { x2, y2 }, { x3, y3 }, }, point); +} - f64 dx = half_w * sqrt(1.0 - ry * ry); +b8 hit_ellipse(Box area, vec2 point) { + f64 dw = area.width / 2; + f64 dh = area.height / 2; - i64 left = (i64) floor(cx - dx + 0.5); - i64 right = (i64) floor(cx + dx - 0.5); + if (dw < EPSILON || dh < EPSILON) + return 0; - if (left < 0) left = 0; - if (right >= dst.width) right = dst.width - 1; + f64 cx = area.x + dw; + f64 cy = area.y + dh; + f64 kx = 1. / dw; + f64 ky = 1. / dh; - vec4_f32 *p = dst.pixels + j * dst.stride + left; - vec4_f32 *p_end = dst.pixels + j * dst.stride + right + 1; - for (; p < p_end; ++p) - put_pixel_(p, color); - } + f64 dx = (point.x - cx) * kx; + f64 dy = (point.y - cy) * ky; -_finish: - PROFILER_end(PROFILE_FILL_ELLIPSE); + return dx * dx + dy * dy - 1.0 < EPSILON; } -static b8 quad_from_line_(vec2 *dst, vec2 vertices[2], f64 width) { +b8 hit_line(vec2 vertices[2], f64 width, vec2 point) { f64 x0 = vertices[0].x; f64 y0 = vertices[0].y; f64 x1 = vertices[1].x; @@ -528,1116 +583,797 @@ static b8 quad_from_line_(vec2 *dst, vec2 vertices[2], f64 width) { // Tangent // f64 tx = -dy; - f64 ty = dx; + f64 ty = dx; f64 tl = sqrt(tx * tx + ty * ty); + if (tl >= EPSILON) { + tx /= tl; + ty /= tl; + } + tx *= width * .5; + ty *= width * .5; - if (tl < EPSILON) - return 0; + return hit_quad((vec2[4]) { + { x0 - tx, y0 - ty }, + { x0 + tx, y0 + ty }, + { x1 + tx, y1 + ty }, + { x1 - tx, y1 - ty }, + }, point); +} - f64 k = .5 / tl; - tx *= width * k; - ty *= width * k; +static void put_pixel_(vec4_f32 *dst, vec4_f32 color) { + if (color.w == 1.f) + *dst = color; + else { + f32 dst_amount = 1. - color.w; - dst[0] = (vec2) { x0 - tx, y0 - ty }; - dst[1] = (vec2) { x0 + tx, y0 + ty }; - dst[2] = (vec2) { x1 + tx, y1 + ty }; - dst[3] = (vec2) { x1 - tx, y1 - ty }; - return 1; + *dst = (vec4_f32) { + .x = dst->x * dst_amount + color.x * color.w, + .y = dst->y * dst_amount + color.y * color.w, + .z = dst->z * dst_amount + color.z * color.w, + .w = max2_f32_(dst->w, color.w), + }; + } } -void fill_line_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[2], f64 width) { - // FIXME, PERF: Use Bresenham's algorithm when width is 1. +void draw_pixels_to_buffer(Pixel_Buffer dst, Box area, Pixel_Buffer src) { + if (area.width < EPSILON || area.height < EPSILON) + return; - if (width < EPSILON) return; - PROFILER_begin(PROFILE_FILL_LINE); - - vec2 quad[4]; - if (!quad_from_line_(quad, vertices, width)) return; - fill_quad_to_buffer(dst, color, quad); + PROFILER_begin(PROFILE_DRAW_PIXELS); - // TODO: Also render line ends as circles, use stencil buffer. - PROFILER_end(PROFILE_FILL_LINE); -} + i64 i0 = (i64) floor(area.x + 0.5); + i64 i1 = (i64) floor(area.x + area.width + 0.5); + i64 j0 = (i64) floor(area.y + 0.5); + i64 j1 = (i64) floor(area.y + area.height + 0.5); -// ================================================================ -// -// LCD text rendering -// -// ================================================================ + if (i0 < 0) i0 = 0; + if (i1 > dst.width) i1 = dst.width; + if (j0 < 0) j0 = 0; + if (j1 > dst.height) j1 = dst.height; -static u64 _lcd_bits[] = { - 0x10001010101ull, - 0x505ull, - 0xa1f0a0a1f0a00ull, - 0x40e140e050e04ull, - 0x814081f020502ull, - 0x7090503060506ull, - 0x101ull, - 0x2010101010102ull, - 0x1020202020201ull, - 0x120c3f0c12ull, - 0x4041f040400ull, - 0x1020000000000ull, - 0x1f000000ull, - 0x10000000000ull, - 0x1010202020404ull, - 0x70505050700ull, - 0x70202020300ull, - 0x70102040700ull, - 0x70402040700ull, - 0x40407050500ull, - 0x30407010700ull, - 0x70507010600ull, - 0x20202040700ull, - 0x70507050700ull, - 0x30407050700ull, - 0x10000010000ull, - 0x1020000020000ull, - 0x40201020400ull, - 0x1f001f0000ull, - 0x10204020100ull, - 0x20002040502ull, - 0xe01191519110eull, - 0x9090f090906ull, - 0x70909070907ull, - 0x60901010906ull, - 0x70909090907ull, - 0xf010107010full, - 0x1010107010full, - 0x6090d010906ull, - 0x909090f0909ull, - 0x70202020207ull, - 0x20504040407ull, - 0x90905030509ull, - 0x70101010101ull, - 0x1111151f1b11ull, - 0x111119151311ull, - 0x60909090906ull, - 0x10107090907ull, - 0x8060909090906ull, - 0x90907090907ull, - 0x60908060106ull, - 0x4040404041full, - 0x60909090909ull, - 0x40a11111111ull, - 0xa1f15151111ull, - 0x11110a040a11ull, - 0x102040a1111ull, - 0xf010204080full, - 0x3010101010103ull, - 0x4040202020101ull, - 0x3020202020203ull, - 0x110a04ull, - 0x3f000000000000ull, - 0x201ull, - 0x6090f080600ull, - 0x70909070100ull, - 0x60901090600ull, - 0xe09090e0800ull, - 0x6010f090600ull, - 0x20207020600ull, - 0x6080e09090600ull, - 0x90907010100ull, - 0x70203000200ull, - 0x1020203000200ull, - 0x50503050500ull, - 0x20101010101ull, - 0x151515150f00ull, - 0x90909090700ull, - 0x60909090600ull, - 0x10709090700ull, - 0x80e09090e00ull, - 0x10101030500ull, - 0x60806010600ull, - 0x20202020700ull, - 0xe0909090900ull, - 0x40a11111100ull, - 0xa1f15111100ull, - 0x110a040a1100ull, - 0x102040a1100ull, - 0xf0106080f00ull, - 0x4020201020204ull, - 0x1010101010101ull, - 0x1020204020201ull, - 0x1926ull, -}; + // FIXME, PERF: Use integer arithmetic. -enum { - LCD_Width = 6, - LCD_Height = 7, - LCD_Num_Bits = LCD_Width * LCD_Height, - LCD_First = 33, - LCD_Space = 4, - LCD_Len = sizeof(_lcd_bits) / sizeof(*_lcd_bits), -}; + f64 di = src.width / area.width; + f64 dj = src.height / area.height; + f64 jj = (j0 - area.y) * dj + dj * .5; + for (i64 j = j0; j < j1; ++j, jj += dj) { + if (jj < 0 || jj >= src.height) continue; + vec4_f32 *d = dst.pixels + j * dst.stride + i0; + vec4_f32 *d_end = d + i1 - i0; + vec4_f32 *s = src.pixels + (i64) jj * src.stride; + f64 ii = (i0 - area.x) * di + di * .5; + if (ii < 0 || ii >= src.width) continue; + for (; d < d_end; ++d, ii += di) + put_pixel_(d, s[(i64) ii]); + } -static u64 lcd_column_convolved_(c32 c, i64 column_index) { - if (column_index < 0 || column_index >= LCD_Width) - return 0; + PROFILER_end(PROFILE_DRAW_PIXELS); +} - i64 index = c - LCD_First; - if (index < 0 || index >= LCD_Len) - return 0; +void fill_rectangle_to_buffer(Pixel_Buffer dst, vec4_f32 color, Box area) { + PROFILER_begin(PROFILE_FILL_RECTANGLE); - u64 column = 0; - for (i64 y = 0; y < LCD_Height; ++y) - if ((_lcd_bits[index] & (1ull << (y * 8 + column_index))) != 0) - column |= 3ull << y; - return column; -} + i64 i0 = (i64) floor(area.x + 0.5); + i64 i1 = (i64) floor(area.x + area.width + 0.5); + i64 j0 = (i64) floor(area.y + 0.5); + i64 j1 = (i64) floor(area.y + area.height + 0.5); -static b8 lcd_column_empty_(c32 c, i64 column_index) { - if (column_index < 0 || column_index >= LCD_Width) - return 1; + if (i0 < 0) i0 = 0; + if (i1 > dst.width) i1 = dst.width; + if (j0 < 0) j0 = 0; + if (j1 > dst.height) j1 = dst.height; - i64 index = c - LCD_First; - if (index < 0 || index >= LCD_Len) - return 1; + for (i64 j = j0; j < j1; ++j) { + vec4_f32 *p = dst.pixels + j * dst.stride + i0; + vec4_f32 *p_end = dst.pixels + j * dst.stride + i1; + for (; p < p_end; ++p) + put_pixel_(p, color); + } - for (i64 y = 0; y < LCD_Height; ++y) - if ((_lcd_bits[index] & (1ull << (y * 8 + column_index))) != 0) - return 0; - return 1; + PROFILER_end(PROFILE_FILL_RECTANGLE); } -static i64 lcd_char_width_(c32 c) { - // FIXME, PERF: Pre-calculate dimensions. +void fill_triangle_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[3]) { + PROFILER_begin(PROFILE_FILL_TRIANGLE); - i64 index = c - LCD_First; - if (index < -1) - return 0; - if (index < 0 || index >= LCD_Len) - return LCD_Space; + f64 x0 = vertices[0].x; + f64 y0 = vertices[0].y; + f64 x1 = vertices[1].x; + f64 y1 = vertices[1].y; + f64 x2 = vertices[2].x; + f64 y2 = vertices[2].y; - i64 width = 0; - for (; width < LCD_Width; ++width) - if (lcd_column_empty_(c, width) && lcd_column_empty_(c, width + 1)) - break; - return width; -} + i64 i0 = (i64) floor(min3_(x0, x1, x2)); + i64 j0 = (i64) floor(min3_(y0, y1, y2)); + i64 i1 = (i64) ceil (max3_(x0, x1, x2)); + i64 j1 = (i64) ceil (max3_(y0, y1, y2)); -static i64 lcd_spacing_(i64 num_chars, c32 *text, i64 index) { - // FIXME, PERF: Pre-calculate kerning. + if (i0 < 0) i0 = 0; + if (i1 >= dst.width) i1 = dst.width - 1; + if (j0 < 0) j0 = 0; + if (j1 >= dst.height) j1 = dst.height - 1; - if (text == NULL) - return 0; + for (i64 j = j0; j <= j1; ++j) { + // FIXME, PERF: Calculate the exact intersection. - if (index < 0 || index + 1 >= num_chars) - return 0; + i64 left = i1; + i64 right = i0; - u64 a = lcd_column_convolved_(text[index], lcd_char_width_(text[index]) - 1); - u64 b = lcd_column_convolved_(text[index + 1], 0); + for (i64 i = i0; i <= i1; ++i) + if (hit_triangle(vertices, (vec2) { (f64) i, (f64) j, })) { + left = i; + break; + } - if (!!(a & b)) - return 1; + for (i64 i = i1; i >= i0; --i) + if (hit_triangle(vertices, (vec2) { (f64) i, (f64) j, })) { + right = i; + break; + } - return 0; + vec4_f32 *p = dst.pixels + j * dst.stride + left; + vec4_f32 *p_end = dst.pixels + j * dst.stride + right + 1; + for (; p < p_end; ++p) + put_pixel_(p, color); + } + + PROFILER_end(PROFILE_FILL_TRIANGLE); } -static i64 lcd_cursor_(i64 num_chars, c32 *text) { - if (text == NULL) - return 0; +static void triangles_area_(f64 *x0, f64 *y0, f64 *x1, f64 *y1, vec2 p, vec2 s, i64 num, vec2 *v) { + if (num <= 0) + return; - i64 cursor = 0; + *x0 = min3_(p.x + v[0].x * s.x, p.x + v[1].x * s.x, p.x + v[2].x * s.x); + *y0 = min3_(p.y + v[0].y * s.y, p.y + v[1].y * s.y, p.y + v[2].y * s.y); + *x1 = max3_(p.x + v[0].x * s.x, p.x + v[1].x * s.x, p.x + v[2].x * s.x); + *y1 = max3_(p.y + v[0].y * s.y, p.y + v[1].y * s.y, p.y + v[2].y * s.y); - for (i64 i = 0; i < num_chars; ++i) { - if (text[i] <= ' ') { - if (text[i] == '\n') - cursor = 0; - else if (text[i] == '\b' && i > 0) - cursor -= lcd_char_width_(text[i - 1]) + lcd_spacing_(num_chars, text, i - 1); - else if (text[i] == '\r') - cursor = 0; - else - cursor += lcd_char_width_(' ') + lcd_spacing_(num_chars, text, i); - continue; - } - cursor += lcd_char_width_(text[i]) + lcd_spacing_(num_chars, text, i); + for (i64 i = 1; i < num; ++i) { + *x0 = min4_(*x0, p.x + v[i * 3].x * s.x, p.x + v[i * 3 + 1].x * s.x, p.x + v[i * 3 + 2].x * s.x); + *y0 = min4_(*y0, p.y + v[i * 3].y * s.y, p.y + v[i * 3 + 1].y * s.y, p.y + v[i * 3 + 2].y * s.y); + *x1 = max4_(*x1, p.x + v[i * 3].x * s.x, p.x + v[i * 3 + 1].x * s.x, p.x + v[i * 3 + 2].x * s.x); + *y1 = max4_(*y1, p.y + v[i * 3].y * s.y, p.y + v[i * 3 + 1].y * s.y, p.y + v[i * 3 + 2].y * s.y); } - - return cursor; } -static i64 lcd_enum_text_columns_(i64 num_chars, c32 *text) { - if (text == NULL) - return 0; +void fill_triangles_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices) { + PROFILER_begin(PROFILE_FILL_TRIANGLES); - i64 cols = 0; - i64 n = 0; + f64 x0, y0, x1, y1; + triangles_area_(&x0, &y0, &x1, &y1, position, scale, num_triangles, vertices); - for (i64 i = 0; i < num_chars; ++i) { - if (text[i] <= ' ') { - if (text[i] == '\n') { - if (cols < n) - cols = n; - n = 0; - } else if (text[i] == '\b' && i > 0) { - if (cols < n) - cols = n; - n -= lcd_char_width_(text[i - 1]) + lcd_spacing_(num_chars, text, i - 1); - } else if (text[i] == '\r') { - if (cols < n) - cols = n; - n = 0; - } else - n += lcd_char_width_(' ') + lcd_spacing_(num_chars, text, i); - continue; - } - n += lcd_char_width_(text[i]) + lcd_spacing_(num_chars, text, i); - } + i64 i0 = (i64) floor(x0); + i64 j0 = (i64) floor(y0); + i64 i1 = (i64) ceil (x1); + i64 j1 = (i64) ceil (y1); - if (cols < n) - cols = n; + if (i0 < 0) i0 = 0; + if (i1 >= dst.width) i1 = dst.width - 1; + if (j0 < 0) j0 = 0; + if (j1 >= dst.height) j1 = dst.height - 1; - return cols; -} + // FIXME, PERF: Use stencil buffer. -static i64 lcd_enum_text_rows_(i64 num_chars, c32 *text) { - if (text == NULL) - return 0; + for (i64 j = j0; j <= j1; ++j) + for (i64 i = i0; i <= i1; ++i) + for (i64 k = 0; k < num_triangles; ++k) { + vec2 triangle[3] = { + vertices[3 * k], + vertices[3 * k + 1], + vertices[3 * k + 2], + }; - i64 rows = 0; + for (i64 r = 0; r < 3; ++r) { + triangle[r].x = position.x + triangle[r].x * scale. x; + triangle[r].y = position.y + triangle[r].y * scale.y; + } - for (i64 i = 0; i <= num_chars; ++i) - if (i == num_chars || text[i] == '\n') { - if (rows > 0) - ++rows; - rows += LCD_Height; - } + if (hit_triangle(triangle, (vec2) { (f64) i, (f64) j, })) { + put_pixel_(dst.pixels + j * dst.stride + i, color); + break; + } + } - return rows; + PROFILER_end(PROFILE_FILL_TRIANGLES); } -static void lcd_draw_text_(Pixel_Buffer dst, vec4_f32 color, f64 x0, f64 y0, f64 scale_x, f64 scale_y, i64 num_chars, c32 *text) { - f64 kx = scale_x; - f64 h = scale_y * LCD_Height; - - if (h < EPSILON) - return; - - f64 x = x0; - f64 y = y0; - - for (i64 n = 0; n < num_chars; ++n) { - if (text[n] <= ' ') { - if (text[n] == '\n') { - x = x0; - y += scale_y * (LCD_Height + 1); - } - else if (text[n] == '\b' && n > 0) - x -= kx * (lcd_char_width_(text[n - 1]) + lcd_spacing_(num_chars, text, n - 1)); - else if (text[n] == '\r') - x = x0; - else - x += kx * (lcd_char_width_(' ') + lcd_spacing_(num_chars, text, n)); - continue; - } +void fill_quad_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[4]) { + PROFILER_begin(PROFILE_FILL_QUAD); - i64 num_cols = lcd_char_width_(text[n]); - f64 w = num_cols * kx; - i64 index = text[n] - LCD_First; + f64 x0 = vertices[0].x; + f64 y0 = vertices[0].y; + f64 x1 = vertices[1].x; + f64 y1 = vertices[1].y; + f64 x2 = vertices[2].x; + f64 y2 = vertices[2].y; + f64 x3 = vertices[3].x; + f64 y3 = vertices[3].y; - if (w >= EPSILON && index >= 0 && index < LCD_Len) { - i64 i0 = (i64) floor(x + 0.5); - i64 i1 = (i64) floor(x + w - 0.5); - i64 j0 = (i64) floor(y + 0.5); - i64 j1 = (i64) floor(y + h - 0.5); + i64 i0 = (i64) floor(min4_(x0, x1, x2, x3)); + i64 j0 = (i64) floor(min4_(y0, y1, y2, y3)); + i64 i1 = (i64) ceil (max4_(x0, x1, x2, x3)); + i64 j1 = (i64) ceil (max4_(y0, y1, y2, y3)); - if (i0 < 0) i0 = 0; - if (i1 >= dst.width) i1 = dst.width - 1; - if (j0 < 0) j0 = 0; - if (j1 >= dst.height) j1 = dst.height - 1; + if (i0 < 0) i0 = 0; + if (i1 >= dst.width) i1 = dst.width - 1; + if (j0 < 0) j0 = 0; + if (j1 >= dst.height) j1 = dst.height - 1; - f64 w_inv = 1. / w; - f64 h_inv = 1. / h; + for (i64 j = j0; j <= j1; ++j) { + // FIXME, PERF: Calculate the exact intersection. - // FIXME, PERF: Use integer arithmetic. + i64 left = i1; + i64 right = i0; - for (i64 j = j0; j <= j1; ++j) { - i64 row = (i64) floor(((j - y + 0.5) * LCD_Height) * h_inv); - for (i64 i = i0; i <= i1; ++i) { - i64 column = (i64) floor(((i - x + 0.5) * num_cols) * w_inv); + for (i64 i = i0; i <= i1; ++i) + if (hit_quad(vertices, (vec2) { (f64) i, (f64) j, })) { + left = i; + break; + } - if ((_lcd_bits[index] & (1ull << (row * 8 + column))) != 0) - put_pixel_(dst.pixels + j * dst.stride + i, color); - } + for (i64 i = i1; i >= i0; --i) + if (hit_quad(vertices, (vec2) { (f64) i, (f64) j, })) { + right = i; + break; } - } - x += kx * (num_cols + lcd_spacing_(num_chars, text, n)); + vec4_f32 *p = dst.pixels + j * dst.stride + left; + vec4_f32 *p_end = dst.pixels + j * dst.stride + right + 1; + for (; p < p_end; ++p) + put_pixel_(p, color); } -} - -#if !defined(GRAPHICS_ENABLE_FONT_CUSTOM_DISPATCH) -Box font_text_area_dispatch(i32 font, vec2 scale, i64 num_chars, c32 *text) { - // TODO - (void) font; - (void) scale; - (void) num_chars; - (void) text; - - return (Box) {0}; + PROFILER_end(PROFILE_FILL_QUAD); } -void font_render_to_stencil_dispatch(Stencil_Buffer dst, i32 font, vec2 position, vec2 scale, i64 num_chars, c32 *text) { - // TODO +void fill_ellipse_to_buffer(Pixel_Buffer dst, vec4_f32 color, Box area) { + PROFILER_begin(PROFILE_FILL_ELLIPSE); - (void) dst; - (void) font; - (void) position; - (void) scale; - (void) num_chars; - (void) text; -} -#endif // !defined(GRAPHICS_ENABLE_FONT_CUSTOM_DISPATCH) + f64 y1 = area.y + area.height; -void draw_text_area_to_buffer(Pixel_Buffer dst, i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text) { - (void) font; + i64 j0 = (i64) floor(area.y); + i64 j1 = (i64) ceil (y1); - if (text == NULL || num_chars <= 0 || max_size.x < EPSILON || max_size.y < EPSILON) - return; + if (j0 < 0) j0 = 0; + if (j1 >= dst.height) j1 = dst.height - 1; - PROFILER_begin(PROFILE_DRAW_TEXT_AREA); + f64 half_w = area.width / 2.0; + f64 half_h = area.height / 2.0; - i64 num_columns = lcd_enum_text_columns_(num_chars, text); - i64 num_rows = lcd_enum_text_rows_(num_chars, text); + if (half_h < EPSILON) + goto _finish; + f64 inv_half_h = 1.0 / half_h; - f64 scale_x = area.width / num_columns; - f64 scale_y = area.height / num_rows; + f64 cx = area.x + half_w; + f64 cy = area.y + half_h; - f64 kx = scale_x / max_size.x; - f64 ky = scale_y / max_size.y; + for (i64 j = j0; j <= j1; ++j) { + f64 ry = (j - cy + 0.5) * inv_half_h; + if (ry > 1.0 - EPSILON || ry < -1.0 + EPSILON) + continue; - f64 k = kx < ky ? kx : ky; + f64 dx = half_w * sqrt(1.0 - ry * ry); - kx = k * max_size.x; - ky = k * max_size.y; + i64 left = (i64) floor(cx - dx + 0.5); + i64 right = (i64) floor(cx + dx - 0.5); - lcd_draw_text_(dst, color, area.x, area.y, kx, ky, num_chars, text); + if (left < 0) left = 0; + if (right >= dst.width) right = dst.width - 1; - PROFILER_end(PROFILE_DRAW_TEXT_AREA); -} + vec4_f32 *p = dst.pixels + j * dst.stride + left; + vec4_f32 *p_end = dst.pixels + j * dst.stride + right + 1; + for (; p < p_end; ++p) + put_pixel_(p, color); + } -void draw_text_cursor_to_buffer(Pixel_Buffer dst, i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection) { - (void) font; +_finish: + PROFILER_end(PROFILE_FILL_ELLIPSE); +} - if (max_size.x < EPSILON || max_size.y < EPSILON) - return; +static b8 quad_from_line_(vec2 *dst, vec2 vertices[2], f64 width) { + f64 x0 = vertices[0].x; + f64 y0 = vertices[0].y; + f64 x1 = vertices[1].x; + f64 y1 = vertices[1].y; - PROFILER_begin(PROFILE_DRAW_TEXT_CURSOR); + f64 dx = x1 - x0; + f64 dy = y1 - y0; - i64 num_columns = lcd_enum_text_columns_(num_chars, text); - i64 num_rows = lcd_enum_text_rows_(num_chars, text); - i64 cursor_x = lcd_cursor_(cursor, text); - i64 cursor_y = lcd_enum_text_rows_(cursor, text); - - f64 scale_x = area.width / num_columns; - f64 scale_y = area.height / num_rows; + // Tangent + // + f64 tx = -dy; + f64 ty = dx; + f64 tl = sqrt(tx * tx + ty * ty); - f64 kx = scale_x / max_size.x; - f64 ky = scale_y / max_size.y; + if (tl < EPSILON) + return 0; - f64 k = kx < ky ? kx : ky; + f64 k = .5 / tl; + tx *= width * k; + ty *= width * k; - kx = k * max_size.x; - ky = k * max_size.y; + dst[0] = (vec2) { x0 - tx, y0 - ty }; + dst[1] = (vec2) { x0 + tx, y0 + ty }; + dst[2] = (vec2) { x1 + tx, y1 + ty }; + dst[3] = (vec2) { x1 - tx, y1 - ty }; + return 1; +} - if (selection != 0) { - i64 selection_x, selection_y; +void fill_line_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[2], f64 width) { + // FIXME, PERF: Use Bresenham's algorithm when width is 1. - if (selection > 0) { - selection_x = lcd_cursor_(cursor + selection, text); - selection_y = lcd_enum_text_rows_(cursor + selection, text); - } else { - selection_x = cursor_x; - selection_y = cursor_y; - cursor_x = lcd_cursor_(cursor + selection, text); - cursor_y = lcd_enum_text_rows_(cursor + selection, text); - } + if (width < EPSILON) return; + PROFILER_begin(PROFILE_FILL_LINE); - if (cursor_y == selection_y) - fill_rectangle_to_buffer( - dst, - color, - (Box) { - .x = area.x + kx * cursor_x, - .y = area.y + ky * cursor_y - ky * (LCD_Height + 1), - .width = kx * (selection_x - cursor_x), - .height = ky * (LCD_Height + 1), - } - ); - else { - fill_rectangle_to_buffer( - dst, - color, - (Box) { - .x = area.x + kx * cursor_x, - .y = area.y + ky * cursor_y - ky * (LCD_Height + 1), - .width = kx * (num_columns - cursor_x), - .height = ky * (LCD_Height + 1), - } - ); - for (i64 j = cursor_y + LCD_Height + 1; j < selection_y; j += LCD_Height + 1) - fill_rectangle_to_buffer( - dst, - color, - (Box) { - .x = area.x, - .y = area.y + ky * j - ky * (LCD_Height + 1), - .width = kx * num_columns, - .height = ky * (LCD_Height + 1), - } - ); - fill_rectangle_to_buffer( - dst, - color, - (Box) { - .x = area.x, - .y = area.y + ky * selection_y - ky * (LCD_Height + 1), - .width = kx * selection_x, - .height = ky * (LCD_Height + 1), - } - ); - } - } else - fill_rectangle_to_buffer( - dst, - color, - (Box) { - .x = area.x + kx * cursor_x, - .y = area.y + ky * cursor_y - ky * LCD_Height, - .width = kx * .5, - .height = ky * (LCD_Height - 1), - } - ); + vec2 quad[4]; + if (!quad_from_line_(quad, vertices, width)) return; + fill_quad_to_buffer(dst, color, quad); - PROFILER_end(PROFILE_DRAW_TEXT_CURSOR); + // TODO: Also render line ends as circles, use stencil buffer. + PROFILER_end(PROFILE_FILL_LINE); } +// ================================================================ +// +// LCD text rendering +// // ================================================================ -vec3_f32 vec3_from_vec4_f32(vec4_f32 v) { - return (vec3_f32) { - .x = v.x, - .y = v.y, - .z = v.z, - }; -} +static u64 _lcd_bits[] = { + 0x10001010101ull, + 0x505ull, + 0xa1f0a0a1f0a00ull, + 0x40e140e050e04ull, + 0x814081f020502ull, + 0x7090503060506ull, + 0x101ull, + 0x2010101010102ull, + 0x1020202020201ull, + 0x120c3f0c12ull, + 0x4041f040400ull, + 0x1020000000000ull, + 0x1f000000ull, + 0x10000000000ull, + 0x1010202020404ull, + 0x70505050700ull, + 0x70202020300ull, + 0x70102040700ull, + 0x70402040700ull, + 0x40407050500ull, + 0x30407010700ull, + 0x70507010600ull, + 0x20202040700ull, + 0x70507050700ull, + 0x30407050700ull, + 0x10000010000ull, + 0x1020000020000ull, + 0x40201020400ull, + 0x1f001f0000ull, + 0x10204020100ull, + 0x20002040502ull, + 0xe01191519110eull, + 0x9090f090906ull, + 0x70909070907ull, + 0x60901010906ull, + 0x70909090907ull, + 0xf010107010full, + 0x1010107010full, + 0x6090d010906ull, + 0x909090f0909ull, + 0x70202020207ull, + 0x20504040407ull, + 0x90905030509ull, + 0x70101010101ull, + 0x1111151f1b11ull, + 0x111119151311ull, + 0x60909090906ull, + 0x10107090907ull, + 0x8060909090906ull, + 0x90907090907ull, + 0x60908060106ull, + 0x4040404041full, + 0x60909090909ull, + 0x40a11111111ull, + 0xa1f15151111ull, + 0x11110a040a11ull, + 0x102040a1111ull, + 0xf010204080full, + 0x3010101010103ull, + 0x4040202020101ull, + 0x3020202020203ull, + 0x110a04ull, + 0x3f000000000000ull, + 0x201ull, + 0x6090f080600ull, + 0x70909070100ull, + 0x60901090600ull, + 0xe09090e0800ull, + 0x6010f090600ull, + 0x20207020600ull, + 0x6080e09090600ull, + 0x90907010100ull, + 0x70203000200ull, + 0x1020203000200ull, + 0x50503050500ull, + 0x20101010101ull, + 0x151515150f00ull, + 0x90909090700ull, + 0x60909090600ull, + 0x10709090700ull, + 0x80e09090e00ull, + 0x10101030500ull, + 0x60806010600ull, + 0x20202020700ull, + 0xe0909090900ull, + 0x40a11111100ull, + 0xa1f15111100ull, + 0x110a040a1100ull, + 0x102040a1100ull, + 0xf0106080f00ull, + 0x4020201020204ull, + 0x1010101010101ull, + 0x1020204020201ull, + 0x1926ull, +}; -vec4_f32 vec4_from_vec3_f32(vec3_f32 v, f32 w) { - return (vec4_f32) { - .x = v.x, - .y = v.y, - .z = v.z, - .w = w, - }; -} +enum { + LCD_Width = 6, + LCD_Height = 7, + LCD_Num_Bits = LCD_Width * LCD_Height, + LCD_First = 33, + LCD_Space = 4, + LCD_Len = sizeof(_lcd_bits) / sizeof(*_lcd_bits), +}; -vec3_f32 vec3_f32_lerp(vec3_f32 a, vec3_f32 b, f32 t) { - return (vec3_f32) { - .x = a.x + (b.x - a.x) * t, - .y = a.y + (b.y - a.y) * t, - .z = a.z + (b.z - a.z) * t, - }; -} +static u64 lcd_column_convolved_(c32 c, i64 column_index) { + if (column_index < 0 || column_index >= LCD_Width) + return 0; -vec4_f32 vec4_f32_lerp(vec4_f32 a, vec4_f32 b, f32 t) { - return (vec4_f32) { - .x = a.x + (b.x - a.x) * t, - .y = a.y + (b.y - a.y) * t, - .z = a.z + (b.z - a.z) * t, - .w = a.w + (b.w - a.w) * t, - }; + i64 index = c - LCD_First; + if (index < 0 || index >= LCD_Len) + return 0; + + u64 column = 0; + for (i64 y = 0; y < LCD_Height; ++y) + if ((_lcd_bits[index] & (1ull << (y * 8 + column_index))) != 0) + column |= 3ull << y; + return column; } -// ================================================================ +static b8 lcd_column_empty_(c32 c, i64 column_index) { + if (column_index < 0 || column_index >= LCD_Width) + return 1; -vec3_f32 rgb_gamma_add(vec3_f32 rgb) { - return (vec3_f32) { - .x = gamma_(rgb.x), - .y = gamma_(rgb.y), - .z = gamma_(rgb.z), - }; -} + i64 index = c - LCD_First; + if (index < 0 || index >= LCD_Len) + return 1; -vec3_f32 rgb_gamma_remove(vec3_f32 rgb) { - return (vec3_f32) { - .x = gamma_re_(rgb.x), - .y = gamma_re_(rgb.y), - .z = gamma_re_(rgb.z), - }; + for (i64 y = 0; y < LCD_Height; ++y) + if ((_lcd_bits[index] & (1ull << (y * 8 + column_index))) != 0) + return 0; + return 1; } -vec4_f32 rgba_gamma_add(vec4_f32 rgba) { - return (vec4_f32) { - .x = gamma_(rgba.x), - .y = gamma_(rgba.y), - .z = gamma_(rgba.z), - .w = gamma_(rgba.w), - }; -} +static i64 lcd_char_width_(c32 c) { + // FIXME, PERF: Pre-calculate dimensions. -vec4_f32 rgba_gamma_remove(vec4_f32 rgba) { - return (vec4_f32) { - .x = gamma_re_(rgba.x), - .y = gamma_re_(rgba.y), - .z = gamma_re_(rgba.z), - .w = gamma_re_(rgba.w), - }; + i64 index = c - LCD_First; + if (index < -1) + return 0; + if (index < 0 || index >= LCD_Len) + return LCD_Space; + + i64 width = 0; + for (; width < LCD_Width; ++width) + if (lcd_column_empty_(c, width) && lcd_column_empty_(c, width + 1)) + break; + return width; } -vec3_f32 lab_from_rgb(vec3_f32 rgb) { - f64 l = 0.4122214708 * rgb.x + 0.5363325363 * rgb.y + 0.0514459929 * rgb.z; - f64 m = 0.2119034982 * rgb.x + 0.6806995451 * rgb.y + 0.1073969566 * rgb.z; - f64 s = 0.0883024619 * rgb.x + 0.2817188376 * rgb.y + 0.6299787005 * rgb.z; +static i64 lcd_spacing_(i64 num_chars, c32 *text, i64 index) { + // FIXME, PERF: Pre-calculate kerning. - f64 l_ = cbrt(l); - f64 m_ = cbrt(m); - f64 s_ = cbrt(s); + if (text == NULL) + return 0; - return (vec3_f32) { - .x = (f32) (0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_), - .y = (f32) (1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_), - .z = (f32) (0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_), - }; -} + if (index < 0 || index + 1 >= num_chars) + return 0; -vec3_f32 rgb_from_lab(vec3_f32 lab) { - f64 l_ = lab.x + 0.3963377774 * lab.y + 0.2158037573 * lab.z; - f64 m_ = lab.x - 0.1055613458 * lab.y - 0.0638541728 * lab.z; - f64 s_ = lab.x - 0.0894841775 * lab.y - 1.2914855480 * lab.z; + u64 a = lcd_column_convolved_(text[index], lcd_char_width_(text[index]) - 1); + u64 b = lcd_column_convolved_(text[index + 1], 0); - f64 l = l_ * l_ * l_; - f64 m = m_ * m_ * m_; - f64 s = s_ * s_ * s_; + if (!!(a & b)) + return 1; - return (vec3_f32) { - .x = (f32) (+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s), - .y = (f32) (-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s), - .z = (f32) (-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s), - }; + return 0; } -vec3_f32 lch_from_lab(vec3_f32 lab) { - f64 a = lab.y; - f64 b = lab.z; +static i64 lcd_cursor_(i64 num_chars, c32 *text) { + if (text == NULL) + return 0; - return (vec3_f32) { - .x = lab.x, - .y = (f32) sqrt (a * a + b * b), - .z = (f32) atan2(b, a), - }; -} + i64 cursor = 0; -vec3_f32 lab_from_lch(vec3_f32 lch) { - return (vec3_f32) { - .x = lch.x, - .y = (f32) (lch.y * cos(lch.z)), - .z = (f32) (lch.y * sin(lch.z)), - }; -} + for (i64 i = 0; i < num_chars; ++i) { + if (text[i] <= ' ') { + if (text[i] == '\n') + cursor = 0; + else if (text[i] == '\b' && i > 0) + cursor -= lcd_char_width_(text[i - 1]) + lcd_spacing_(num_chars, text, i - 1); + else if (text[i] == '\r') + cursor = 0; + else + cursor += lcd_char_width_(' ') + lcd_spacing_(num_chars, text, i); + continue; + } + cursor += lcd_char_width_(text[i]) + lcd_spacing_(num_chars, text, i); + } -vec3_f32 rgb_from_lch(vec3_f32 lch) { - return rgb_from_lab(lab_from_lch(lch)); + return cursor; } -vec3_f32 lch_from_rgb(vec3_f32 rgb) { - return lch_from_lab(lab_from_rgb(rgb)); -} +static i64 lcd_enum_text_columns_(i64 num_chars, c32 *text) { + if (text == NULL) + return 0; -vec3_f32 rgb_gamma_lerp (vec3_f32 a, vec3_f32 b, f32 t) { - return rgb_gamma_add(vec3_f32_lerp( - rgb_gamma_remove(a), - rgb_gamma_remove(b), - t - )); -} + i64 cols = 0; + i64 n = 0; -vec4_f32 rgba_gamma_lerp(vec4_f32 a, vec4_f32 b, f32 t) { - return rgba_gamma_add(vec4_f32_lerp( - rgba_gamma_remove(a), - rgba_gamma_remove(b), - t - )); -} + for (i64 i = 0; i < num_chars; ++i) { + if (text[i] <= ' ') { + if (text[i] == '\n') { + if (cols < n) + cols = n; + n = 0; + } else if (text[i] == '\b' && i > 0) { + if (cols < n) + cols = n; + n -= lcd_char_width_(text[i - 1]) + lcd_spacing_(num_chars, text, i - 1); + } else if (text[i] == '\r') { + if (cols < n) + cols = n; + n = 0; + } else + n += lcd_char_width_(' ') + lcd_spacing_(num_chars, text, i); + continue; + } + n += lcd_char_width_(text[i]) + lcd_spacing_(num_chars, text, i); + } -vec3_f32 rgb_mix(vec3_f32 a, vec3_f32 b, f32 t) { - vec3_f32 a_lch = lch_from_rgb(a); - vec3_f32 b_lch = lch_from_rgb(b); + if (cols < n) + cols = n; - return rgb_from_lch((vec3_f32) { - a_lch.x + (b_lch.x - a_lch.x) * t, - a_lch.y + (b_lch.y - a_lch.y) * t, - lch_from_rgb(rgb_gamma_lerp(a, b, t)).z, - }); + return cols; } -vec4_f32 rgba_mix(vec4_f32 a, vec4_f32 b, f32 t) { - vec3_f32 a_lch = lch_from_rgb(vec3_from_vec4_f32(a)); - vec3_f32 b_lch = lch_from_rgb(vec3_from_vec4_f32(b)); - vec4_f32 ab = rgba_gamma_lerp(a, b, t); - - return vec4_from_vec3_f32(rgb_from_lch((vec3_f32) { - a_lch.x + (b_lch.x - a_lch.x) * t, - a_lch.y + (b_lch.y - a_lch.y) * t, - lch_from_rgb(vec3_from_vec4_f32(ab)).z, - }), ab.w); -} +static i64 lcd_enum_text_rows_(i64 num_chars, c32 *text) { + if (text == NULL) + return 0; -vec3_f32 lch_mix(vec3_f32 a, vec3_f32 b, f32 t) { - return (vec3_f32) { - a.x + (b.x - a.x) * t, - a.y + (b.y - a.y) * t, - lch_from_rgb(rgb_gamma_lerp(rgb_from_lch(a), rgb_from_lch(b), t)).z, - }; -} + i64 rows = 0; -vec3_f32 lch_lerp(vec3_f32 a, vec3_f32 b, f32 t) { - f32 delta_hue = b.z - a.z; - if (delta_hue > M_PI) delta_hue -= M_PI * 2; - if (delta_hue < -M_PI) delta_hue += M_PI * 2; + for (i64 i = 0; i <= num_chars; ++i) + if (i == num_chars || text[i] == '\n') { + if (rows > 0) + ++rows; + rows += LCD_Height; + } - return (vec3_f32) { - .x = a.x + (b.x - a.x) * t, - .y = a.y + (b.y - a.y) * t, - .z = a.z + delta_hue * t, - }; + return rows; } -u32 rgb_u32_from_f32(vec3_f32 color) { - i32 ir = (i32) floor(color.x * 255. + .5); - i32 ig = (i32) floor(color.y * 255. + .5); - i32 ib = (i32) floor(color.z * 255. + .5); +static void lcd_draw_text_(Pixel_Buffer dst, vec4_f32 color, f64 x0, f64 y0, f64 scale_x, f64 scale_y, i64 num_chars, c32 *text) { + f64 kx = scale_x; + f64 h = scale_y * LCD_Height; - u32 r = ir < 0 ? 0u : ir > 255 ? 255u : (u32) ir; - u32 g = ig < 0 ? 0u : ig > 255 ? 255u : (u32) ig; - u32 b = ib < 0 ? 0u : ib > 255 ? 255u : (u32) ib; + if (h < EPSILON) + return; - return (r << 16) | (g << 8) | b; -} + f64 x = x0; + f64 y = y0; -vec3_f32 rgb_f32_from_u32(u32 color) { - return (vec3_f32) { - .x = ((color & 0xff0000) >> 16) / 255.f, - .y = ((color & 0x00ff00) >> 8) / 255.f, - .z = (color & 0x0000ff) / 255.f, - }; -} + for (i64 n = 0; n < num_chars; ++n) { + if (text[n] <= ' ') { + if (text[n] == '\n') { + x = x0; + y += scale_y * (LCD_Height + 1); + } + else if (text[n] == '\b' && n > 0) + x -= kx * (lcd_char_width_(text[n - 1]) + lcd_spacing_(num_chars, text, n - 1)); + else if (text[n] == '\r') + x = x0; + else + x += kx * (lcd_char_width_(' ') + lcd_spacing_(num_chars, text, n)); + continue; + } -u32 rgba_u32_from_f32(vec4_f32 color) { - i32 ir = (i32) floor(color.x * 255. + .5); - i32 ig = (i32) floor(color.y * 255. + .5); - i32 ib = (i32) floor(color.z * 255. + .5); - i32 ia = (i32) floor(color.w * 255. + .5); + i64 num_cols = lcd_char_width_(text[n]); + f64 w = num_cols * kx; + i64 index = text[n] - LCD_First; - u32 r = ir < 0 ? 0u : ir > 255 ? 255u : (u32) ir; - u32 g = ig < 0 ? 0u : ig > 255 ? 255u : (u32) ig; - u32 b = ib < 0 ? 0u : ib > 255 ? 255u : (u32) ib; - u32 a = ia < 0 ? 0u : ia > 255 ? 255u : (u32) ia; - - return (a << 24) | (r << 16) | (g << 8) | b; -} - -vec4_f32 rgba_f32_from_u32(u32 color) { - return (vec4_f32) { - .x = ((color & 0x00ff0000) >> 16) / 255.f, - .y = ((color & 0x0000ff00) >> 8) / 255.f, - .z = (color & 0x000000ff) / 255.f, - .w = ((color & 0xff000000) >> 24) / 255.f, - }; -} - -b8 hit_rectangle(Box area, vec2 point) { - return point.x >= area.x && point.x < area.x + area.width && point.y >= area.y && point.y < area.y + area.height; -} - -static b8 same_sign_(f64 a, f64 b) { - if (a >= EPSILON && b <= -EPSILON) return 0; - if (a <= -EPSILON && b >= EPSILON) return 0; - return 1; -} - -b8 hit_triangle(vec2 vertices[3], vec2 point) { - f64 x0 = vertices[0].x; - f64 y0 = vertices[0].y; - f64 x1 = vertices[1].x; - f64 y1 = vertices[1].y; - f64 x2 = vertices[2].x; - f64 y2 = vertices[2].y; - f64 px = point.x; - f64 py = point.y; - - if ( (px < x0 && px < x1 && px < x2) - || (px > x0 && px > x1 && px > x2) - || (py < y0 && py < y1 && py < y2) - || (py > y0 && py > y1 && py > y2) ) - return 0; - - f64 x10 = x1 - x0; - f64 x21 = x2 - x1; - f64 x02 = x0 - x2; - - f64 y10 = y1 - y0; - f64 y21 = y2 - y1; - f64 y02 = y0 - y2; + if (w >= EPSILON && index >= 0 && index < LCD_Len) { + i64 i0 = (i64) floor(x + 0.5); + i64 i1 = (i64) floor(x + w - 0.5); + i64 j0 = (i64) floor(y + 0.5); + i64 j1 = (i64) floor(y + h - 0.5); - // Z-components of cross-products + if (i0 < 0) i0 = 0; + if (i1 >= dst.width) i1 = dst.width - 1; + if (j0 < 0) j0 = 0; + if (j1 >= dst.height) j1 = dst.height - 1; - f64 z0 = x02 * y10 - x10 * y02; - f64 z1 = x10 * y21 - x21 * y10; - f64 z2 = x21 * y02 - x02 * y21; + f64 w_inv = 1. / w; + f64 h_inv = 1. / h; - f64 pz0 = x02 * (py - y0) - (px - x0) * y02; - f64 pz1 = x10 * (py - y1) - (px - x1) * y10; - f64 pz2 = x21 * (py - y2) - (px - x2) * y21; + // FIXME, PERF: Use integer arithmetic. - return same_sign_(z0, pz0) - && same_sign_(z1, pz1) - && same_sign_(z2, pz2); -} + for (i64 j = j0; j <= j1; ++j) { + i64 row = (i64) floor(((j - y + 0.5) * LCD_Height) * h_inv); + for (i64 i = i0; i <= i1; ++i) { + i64 column = (i64) floor(((i - x + 0.5) * num_cols) * w_inv); -b8 hit_quad(vec2 vertices[4], vec2 point) { - f64 x0 = vertices[0].x; - f64 y0 = vertices[0].y; - f64 x1 = vertices[1].x; - f64 y1 = vertices[1].y; - f64 x2 = vertices[2].x; - f64 y2 = vertices[2].y; - f64 x3 = vertices[3].x; - f64 y3 = vertices[3].y; + if ((_lcd_bits[index] & (1ull << (row * 8 + column))) != 0) + put_pixel_(dst.pixels + j * dst.stride + i, color); + } + } + } - return hit_triangle((vec2[3]) { { x0, y0 }, { x1, y1 }, { x2, y2 }, }, point) - || hit_triangle((vec2[3]) { { x0, y0 }, { x2, y2 }, { x3, y3 }, }, point); + x += kx * (num_cols + lcd_spacing_(num_chars, text, n)); + } } -b8 hit_ellipse(Box area, vec2 point) { - f64 dw = area.width / 2; - f64 dh = area.height / 2; +#if !defined(GRAPHICS_ENABLE_FONT_CUSTOM_DISPATCH) +Box font_text_area_dispatch(i32 font, vec2 scale, i64 num_chars, c32 *text) { + // TODO - if (dw < EPSILON || dh < EPSILON) - return 0; + (void) font; + (void) scale; + (void) num_chars; + (void) text; - f64 cx = area.x + dw; - f64 cy = area.y + dh; - f64 kx = 1. / dw; - f64 ky = 1. / dh; + return (Box) {0}; +} - f64 dx = (point.x - cx) * kx; - f64 dy = (point.y - cy) * ky; +void font_render_to_stencil_dispatch(Stencil_Buffer dst, i32 font, vec2 position, vec2 scale, i64 num_chars, c32 *text) { + // TODO - return dx * dx + dy * dy - 1.0 < EPSILON; + (void) dst; + (void) font; + (void) position; + (void) scale; + (void) num_chars; + (void) text; } +#endif // !defined(GRAPHICS_ENABLE_FONT_CUSTOM_DISPATCH) -b8 hit_line(vec2 vertices[2], f64 width, vec2 point) { - f64 x0 = vertices[0].x; - f64 y0 = vertices[0].y; - f64 x1 = vertices[1].x; - f64 y1 = vertices[1].y; - - f64 dx = x1 - x0; - f64 dy = y1 - y0; +void draw_text_area_to_buffer(Pixel_Buffer dst, i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text) { + (void) font; - // Tangent - // - f64 tx = -dy; - f64 ty = dx; - f64 tl = sqrt(tx * tx + ty * ty); - if (tl >= EPSILON) { - tx /= tl; - ty /= tl; - } - tx *= width * .5; - ty *= width * .5; + if (text == NULL || num_chars <= 0 || max_size.x < EPSILON || max_size.y < EPSILON) + return; - return hit_quad((vec2[4]) { - { x0 - tx, y0 - ty }, - { x0 + tx, y0 + ty }, - { x1 + tx, y1 + ty }, - { x1 - tx, y1 - ty }, - }, point); -} + PROFILER_begin(PROFILE_DRAW_TEXT_AREA); -void draw_pixels_cached(Box area, Pixel_Buffer src) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_DRAW_PIXELS, - .pixels = { - .area = area, - .src = src, - }, - } - ); -} + i64 num_columns = lcd_enum_text_columns_(num_chars, text); + i64 num_rows = lcd_enum_text_rows_(num_chars, text); -void fill_rectangle_cached(vec4_f32 color, Box area) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_FILL_RECTANGLE, - .rectangle = { - .color = color, - .area = area, - }, - } - ); -} + f64 scale_x = area.width / num_columns; + f64 scale_y = area.height / num_rows; -void fill_triangle_cached(vec4_f32 color, vec2 vertices[3]) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_FILL_TRIANGLE, - .triangle = { - .color = color, - .vertices = { - vertices[0], - vertices[1], - vertices[2], - }, - }, - } - ); -} + f64 kx = scale_x / max_size.x; + f64 ky = scale_y / max_size.y; -void fill_triangles_cached(vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_FILL_TRIANGLES, - .triangles = { - .color = color, - .position = position, - .scale = scale, - .num_triangles = num_triangles, - .vertices = vertices, - }, - } - ); -} + f64 k = kx < ky ? kx : ky; -void fill_quad_cached(vec4_f32 color, vec2 vertices[4]) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_FILL_QUAD, - .quad = { - .color = color, - .vertices = { - vertices[0], - vertices[1], - vertices[2], - vertices[3], - }, - }, - } - ); -} + kx = k * max_size.x; + ky = k * max_size.y; -void fill_ellipse_cached(vec4_f32 color, Box area) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_FILL_ELLIPSE, - .ellipse = { - .color = color, - .area = area, - }, - } - ); -} + lcd_draw_text_(dst, color, area.x, area.y, kx, ky, num_chars, text); -void fill_line_cached(vec4_f32 color, vec2 vertices[2], f64 width) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_FILL_LINE, - .line = { - .color = color, - .vertices = { vertices[0], vertices[1], }, - .width = width, - }, - } - ); + PROFILER_end(PROFILE_DRAW_TEXT_AREA); } -void draw_text_area_cached(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_DRAW_TEXT_AREA, - .text_area = { - .font = font, - .color = color, - .area = area, - .max_size = max_size, - .num_chars = num_chars, - .text = text, - }, - } - ); -} +void draw_text_cursor_to_buffer(Pixel_Buffer dst, i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection) { + (void) font; -void draw_text_cursor_cached(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection) { - perform_graphics_request( - (Graphics_Context) {0}, - (Graphics_Request) { - .op = GRAPHICS_DRAW_TEXT_CURSOR, - .text_cursor = { - .font = font, - .color = color, - .area = area, - .max_size = max_size, - .num_chars = num_chars, - .text = text, - .cursor = cursor, - .selection = selection, - }, - } - ); -} + if (max_size.x < EPSILON || max_size.y < EPSILON) + return; -void draw_pixels(Box area, Pixel_Buffer src) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_DRAW_PIXELS, - .pixels = { - .area = area, - .src = src, - }, - } - ); -} + PROFILER_begin(PROFILE_DRAW_TEXT_CURSOR); -void fill_rectangle(vec4_f32 color, Box area) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_FILL_RECTANGLE, - .rectangle = { - .color = color, - .area = area, - }, - } - ); -} + i64 num_columns = lcd_enum_text_columns_(num_chars, text); + i64 num_rows = lcd_enum_text_rows_(num_chars, text); + i64 cursor_x = lcd_cursor_(cursor, text); + i64 cursor_y = lcd_enum_text_rows_(cursor, text); -void fill_triangle(vec4_f32 color, vec2 vertices[3]) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_FILL_TRIANGLE, - .triangle = { - .color = color, - .vertices = { - vertices[0], - vertices[1], - vertices[2], - }, - }, - } - ); -} + f64 scale_x = area.width / num_columns; + f64 scale_y = area.height / num_rows; -void fill_triangles(vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_FILL_TRIANGLES, - .triangles = { - .color = color, - .position = position, - .scale = scale, - .num_triangles = num_triangles, - .vertices = vertices, - }, - } - ); -} + f64 kx = scale_x / max_size.x; + f64 ky = scale_y / max_size.y; -void fill_quad(vec4_f32 color, vec2 vertices[4]) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_FILL_QUAD, - .quad = { - .color = color, - .vertices = { - vertices[0], - vertices[1], - vertices[2], - vertices[3], - }, - }, - } - ); -} + f64 k = kx < ky ? kx : ky; -void fill_ellipse(vec4_f32 color, Box area) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_FILL_ELLIPSE, - .ellipse = { - .color = color, - .area = area, - }, - } - ); -} + kx = k * max_size.x; + ky = k * max_size.y; -void fill_line(vec4_f32 color, vec2 vertices[2], f64 width) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_FILL_LINE, - .line = { - .color = color, - .vertices = { vertices[0], vertices[1], }, - .width = width, - }, - } - ); -} + if (selection != 0) { + i64 selection_x, selection_y; -void draw_text_area(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_DRAW_TEXT_AREA, - .text_area = { - .font = font, - .color = color, - .area = area, - .max_size = max_size, - .num_chars = num_chars, - .text = text, - }, + if (selection > 0) { + selection_x = lcd_cursor_(cursor + selection, text); + selection_y = lcd_enum_text_rows_(cursor + selection, text); + } else { + selection_x = cursor_x; + selection_y = cursor_y; + cursor_x = lcd_cursor_(cursor + selection, text); + cursor_y = lcd_enum_text_rows_(cursor + selection, text); } - ); -} -void draw_text_cursor(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection) { - perform_graphics_request( - (Graphics_Context) { .disable_cache = 1, }, - (Graphics_Request) { - .op = GRAPHICS_DRAW_TEXT_CURSOR, - .text_cursor = { - .font = font, - .color = color, - .area = area, - .max_size = max_size, - .num_chars = num_chars, - .text = text, - .cursor = cursor, - .selection = selection, - }, + if (cursor_y == selection_y) + fill_rectangle_to_buffer( + dst, + color, + (Box) { + .x = area.x + kx * cursor_x, + .y = area.y + ky * cursor_y - ky * (LCD_Height + 1), + .width = kx * (selection_x - cursor_x), + .height = ky * (LCD_Height + 1), + } + ); + else { + fill_rectangle_to_buffer( + dst, + color, + (Box) { + .x = area.x + kx * cursor_x, + .y = area.y + ky * cursor_y - ky * (LCD_Height + 1), + .width = kx * (num_columns - cursor_x), + .height = ky * (LCD_Height + 1), + } + ); + for (i64 j = cursor_y + LCD_Height + 1; j < selection_y; j += LCD_Height + 1) + fill_rectangle_to_buffer( + dst, + color, + (Box) { + .x = area.x, + .y = area.y + ky * j - ky * (LCD_Height + 1), + .width = kx * num_columns, + .height = ky * (LCD_Height + 1), + } + ); + fill_rectangle_to_buffer( + dst, + color, + (Box) { + .x = area.x, + .y = area.y + ky * selection_y - ky * (LCD_Height + 1), + .width = kx * selection_x, + .height = ky * (LCD_Height + 1), + } + ); } - ); + } else + fill_rectangle_to_buffer( + dst, + color, + (Box) { + .x = area.x + kx * cursor_x, + .y = area.y + ky * cursor_y - ky * LCD_Height, + .width = kx * .5, + .height = ky * (LCD_Height - 1), + } + ); + + PROFILER_end(PROFILE_DRAW_TEXT_CURSOR); } +// ================================================================ +// +// Requests cache +// // ================================================================ typedef struct { @@ -2061,128 +1797,408 @@ static void perform_graphics_request_to_buffer_(Pixel_Buffer dst, Graphics_Reque } } -static b8 mem_eq_(i64 size, void *x, void *y) { - for (i64 i = 0; i < size; ++i) - if (((u8 *) x)[i] != ((u8 *) y)[i]) - return 0; - return 1; +static b8 mem_eq_(i64 size, void *x, void *y) { + for (i64 i = 0; i < size; ++i) + if (((u8 *) x)[i] != ((u8 *) y)[i]) + return 0; + return 1; +} + +static void copy_pixels_quick_(Pixel_Buffer dst, i64 x, i64 y, Pixel_Buffer src) { + for (i64 j = 0; j < src.height; ++j) { + vec4_f32 *s = src.pixels + j * src.stride; + vec4_f32 *d = dst.pixels + (y + j) * dst.stride + x; + vec4_f32 *d_end = d + src.width; + + for (; d < d_end; ++d, ++s) { + d->x = d->x * (1.f - s->w) + s->x * s->w; + d->y = d->y * (1.f - s->w) + s->y * s->w; + d->z = d->z * (1.f - s->w) + s->z * s->w; + } + } +} + +static void perform_graphics_request_cached_(Pixel_Buffer dst, Graphics_Request req) { + Graphics_Request_Norm_ norm = normalize_graphics_request_(req); + if (norm.width <= 0 || norm.height <= 0) + return; + + Graphics_Request_Id_ id = graphics_request_id_(norm.req); + + b8 reset_slot = 1; + i64 slot = -1; + + i64 n = (i64) (id.index % (GRAPHICS_CACHE_SIZE / GRAPHICS_CACHE_DEPTH)) * GRAPHICS_CACHE_DEPTH; + + for (i64 i = 0; i < GRAPHICS_CACHE_DEPTH; ++i) { + if (!_graphics_cache[n + i].occupied) + continue; + if (!mem_eq_(BLAKE2B_OUTBYTES, _graphics_cache[n + i].id.hash, id.hash)) + continue; + if (!mem_eq_(sizeof req, &_graphics_cache[n + i].req, &norm.req)) + continue; + reset_slot = 0; + slot = n + i; + break; + } + + if (slot == -1) + for (i64 i = 0; i < GRAPHICS_CACHE_DEPTH; ++i) + if (!_graphics_cache[n + i].occupied) { + slot = n + i; + break; + } + + if (slot == -1) { + // Cache overflow. + slot = n; + } + + Graphics_Cache_Slot_ *s = _graphics_cache + slot; + + i64 num_pixels = norm.width * norm.height; + + if (reset_slot) { + s->occupied = 1; + s->id = id; + s->req = norm.req; + s->width = norm.width; + s->height = norm.height; + + if (num_pixels > s->buffer_len) + resize_dynamic_array_exact(&s->buffer_len, (void **) &s->buffer, sizeof *s->buffer, num_pixels); + + if (s->buffer_len < num_pixels) + // Out of memory + s->occupied = 0; + else { + mem_set_(s->buffer, 0, s->buffer_len * sizeof *s->buffer); + perform_graphics_request_to_buffer_((Pixel_Buffer) { + .width = s->width, + .height = s->height, + .stride = s->width, + .pixels = s->buffer, + }, s->req); + } + } + + if (s->occupied) + copy_pixels_quick_(dst, norm.x, norm.y, (Pixel_Buffer) { + .width = s->width, + .height = s->height, + .stride = s->width, + .pixels = s->buffer, + }); + else + perform_graphics_request_to_buffer_(dst, req); +} + +static void scale_and_perform_graphics_request_(Graphics_Context context, Graphics_Request req) { + Graphics_Request scaled = graphics_request_scaled_(req, context.scale); + + if (context.disable_cache) + perform_graphics_request_to_buffer_(context.dst, scaled); + else + perform_graphics_request_cached_(context.dst, scaled); +} + +static Graphics_Context graphics_context_defaults_(Graphics_Context context) { + if (context.dst.pixels == NULL) { + i64 max_height = g_platform.num_pixels / g_platform.frame_width; + + context.dst.width = g_platform.frame_width; + context.dst.height = min2_i64_(max_height, g_platform.frame_height); + context.dst.stride = g_platform.frame_width; + context.dst.pixels = g_platform.pixels; + + if (context.scale.x == 0. && context.scale.y == 0.) + context.scale = (vec2) { + .x = ((f64) g_platform.frame_width) / g_platform.real_width, + .y = ((f64) g_platform.frame_height) / g_platform.real_height, + }; + } + + return context; +} + +void draw_pixels_cached(Box area, Pixel_Buffer src) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_DRAW_PIXELS, + .pixels = { + .area = area, + .src = src, + }, + } + ); +} + +void fill_rectangle_cached(vec4_f32 color, Box area) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_FILL_RECTANGLE, + .rectangle = { + .color = color, + .area = area, + }, + } + ); +} + +void fill_triangle_cached(vec4_f32 color, vec2 vertices[3]) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_FILL_TRIANGLE, + .triangle = { + .color = color, + .vertices = { + vertices[0], + vertices[1], + vertices[2], + }, + }, + } + ); +} + +void fill_triangles_cached(vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_FILL_TRIANGLES, + .triangles = { + .color = color, + .position = position, + .scale = scale, + .num_triangles = num_triangles, + .vertices = vertices, + }, + } + ); +} + +void fill_quad_cached(vec4_f32 color, vec2 vertices[4]) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_FILL_QUAD, + .quad = { + .color = color, + .vertices = { + vertices[0], + vertices[1], + vertices[2], + vertices[3], + }, + }, + } + ); +} + +void fill_ellipse_cached(vec4_f32 color, Box area) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_FILL_ELLIPSE, + .ellipse = { + .color = color, + .area = area, + }, + } + ); +} + +void fill_line_cached(vec4_f32 color, vec2 vertices[2], f64 width) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_FILL_LINE, + .line = { + .color = color, + .vertices = { vertices[0], vertices[1], }, + .width = width, + }, + } + ); +} + +void draw_text_area_cached(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_DRAW_TEXT_AREA, + .text_area = { + .font = font, + .color = color, + .area = area, + .max_size = max_size, + .num_chars = num_chars, + .text = text, + }, + } + ); +} + +void draw_text_cursor_cached(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection) { + perform_graphics_request( + (Graphics_Context) {0}, + (Graphics_Request) { + .op = GRAPHICS_DRAW_TEXT_CURSOR, + .text_cursor = { + .font = font, + .color = color, + .area = area, + .max_size = max_size, + .num_chars = num_chars, + .text = text, + .cursor = cursor, + .selection = selection, + }, + } + ); } -static void copy_pixels_quick_(Pixel_Buffer dst, i64 x, i64 y, Pixel_Buffer src) { - for (i64 j = 0; j < src.height; ++j) { - vec4_f32 *s = src.pixels + j * src.stride; - vec4_f32 *d = dst.pixels + (y + j) * dst.stride + x; - vec4_f32 *d_end = d + src.width; - - for (; d < d_end; ++d, ++s) { - d->x = d->x * (1.f - s->w) + s->x * s->w; - d->y = d->y * (1.f - s->w) + s->y * s->w; - d->z = d->z * (1.f - s->w) + s->z * s->w; +void draw_pixels(Box area, Pixel_Buffer src) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_DRAW_PIXELS, + .pixels = { + .area = area, + .src = src, + }, } - } + ); } -static void perform_graphics_request_cached_(Pixel_Buffer dst, Graphics_Request req) { - Graphics_Request_Norm_ norm = normalize_graphics_request_(req); - if (norm.width <= 0 || norm.height <= 0) - return; - - Graphics_Request_Id_ id = graphics_request_id_(norm.req); - - b8 reset_slot = 1; - i64 slot = -1; - - i64 n = (i64) (id.index % (GRAPHICS_CACHE_SIZE / GRAPHICS_CACHE_DEPTH)) * GRAPHICS_CACHE_DEPTH; - - for (i64 i = 0; i < GRAPHICS_CACHE_DEPTH; ++i) { - if (!_graphics_cache[n + i].occupied) - continue; - if (!mem_eq_(BLAKE2B_OUTBYTES, _graphics_cache[n + i].id.hash, id.hash)) - continue; - if (!mem_eq_(sizeof req, &_graphics_cache[n + i].req, &norm.req)) - continue; - reset_slot = 0; - slot = n + i; - break; - } - - if (slot == -1) - for (i64 i = 0; i < GRAPHICS_CACHE_DEPTH; ++i) - if (!_graphics_cache[n + i].occupied) { - slot = n + i; - break; - } - - if (slot == -1) { - // Cache overflow. - slot = n; - } - - Graphics_Cache_Slot_ *s = _graphics_cache + slot; - - i64 num_pixels = norm.width * norm.height; - - if (reset_slot) { - s->occupied = 1; - s->id = id; - s->req = norm.req; - s->width = norm.width; - s->height = norm.height; - - if (num_pixels > s->buffer_len) - resize_dynamic_array_exact(&s->buffer_len, (void **) &s->buffer, sizeof *s->buffer, num_pixels); - - if (s->buffer_len < num_pixels) - // Out of memory - s->occupied = 0; - else { - mem_set_(s->buffer, 0, s->buffer_len * sizeof *s->buffer); - perform_graphics_request_to_buffer_((Pixel_Buffer) { - .width = s->width, - .height = s->height, - .stride = s->width, - .pixels = s->buffer, - }, s->req); +void fill_rectangle(vec4_f32 color, Box area) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_FILL_RECTANGLE, + .rectangle = { + .color = color, + .area = area, + }, } - } + ); +} - if (s->occupied) - copy_pixels_quick_(dst, norm.x, norm.y, (Pixel_Buffer) { - .width = s->width, - .height = s->height, - .stride = s->width, - .pixels = s->buffer, - }); - else - perform_graphics_request_to_buffer_(dst, req); +void fill_triangle(vec4_f32 color, vec2 vertices[3]) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_FILL_TRIANGLE, + .triangle = { + .color = color, + .vertices = { + vertices[0], + vertices[1], + vertices[2], + }, + }, + } + ); } -static void scale_and_perform_graphics_request_(Graphics_Context context, Graphics_Request req) { - Graphics_Request scaled = graphics_request_scaled_(req, context.scale); +void fill_triangles(vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_FILL_TRIANGLES, + .triangles = { + .color = color, + .position = position, + .scale = scale, + .num_triangles = num_triangles, + .vertices = vertices, + }, + } + ); +} - if (context.disable_cache) - perform_graphics_request_to_buffer_(context.dst, scaled); - else - perform_graphics_request_cached_(context.dst, scaled); +void fill_quad(vec4_f32 color, vec2 vertices[4]) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_FILL_QUAD, + .quad = { + .color = color, + .vertices = { + vertices[0], + vertices[1], + vertices[2], + vertices[3], + }, + }, + } + ); } -static Graphics_Context graphics_context_defaults_(Graphics_Context context) { - if (context.dst.pixels == NULL) { - i64 max_height = g_platform.num_pixels / g_platform.frame_width; +void fill_ellipse(vec4_f32 color, Box area) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_FILL_ELLIPSE, + .ellipse = { + .color = color, + .area = area, + }, + } + ); +} - context.dst.width = g_platform.frame_width; - context.dst.height = min2_i64_(max_height, g_platform.frame_height); - context.dst.stride = g_platform.frame_width; - context.dst.pixels = g_platform.pixels; +void fill_line(vec4_f32 color, vec2 vertices[2], f64 width) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_FILL_LINE, + .line = { + .color = color, + .vertices = { vertices[0], vertices[1], }, + .width = width, + }, + } + ); +} - if (context.scale.x == 0. && context.scale.y == 0.) - context.scale = (vec2) { - .x = ((f64) g_platform.frame_width) / g_platform.real_width, - .y = ((f64) g_platform.frame_height) / g_platform.real_height, - }; - } +void draw_text_area(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_DRAW_TEXT_AREA, + .text_area = { + .font = font, + .color = color, + .area = area, + .max_size = max_size, + .num_chars = num_chars, + .text = text, + }, + } + ); +} - return context; +void draw_text_cursor(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection) { + perform_graphics_request( + (Graphics_Context) { .disable_cache = 1, }, + (Graphics_Request) { + .op = GRAPHICS_DRAW_TEXT_CURSOR, + .text_cursor = { + .font = font, + .color = color, + .area = area, + .max_size = max_size, + .num_chars = num_chars, + .text = text, + .cursor = cursor, + .selection = selection, + }, + } + ); } // ================================================================ -- cgit v1.2.3