// ================================================================ // // graphics.c // // ---------------------------------------------------------------- // // (C) 2025 Mitya Selivanov // // Any use of this code is prohibited. // // ================================================================ #ifndef GRAPHICS_HEADER_GUARD_ #define GRAPHICS_HEADER_GUARD_ #ifdef GRAPHICS_HEADER #define RUNTIME_HEADER #endif #include "runtime.c" // ================================================================ // // Options // // ================================================================ #ifndef EPSILON #define EPSILON (1e-9) #endif #ifndef GRAPHICS_CACHE_DEPTH #define GRAPHICS_CACHE_DEPTH 4 #endif #ifndef GRAPHICS_CACHE_SIZE #define GRAPHICS_CACHE_SIZE (128 * GRAPHICS_CACHE_DEPTH) #endif // ================================================================ // // Vector math // // ================================================================ vec3_f32 vec3_from_vec4_f32(vec4_f32 v); vec4_f32 vec4_from_vec3_f32(vec3_f32 v, f32 w); vec3_f32 vec3_f32_lerp (vec3_f32 a, vec3_f32 b, f32 t); vec4_f32 vec4_f32_lerp (vec4_f32 a, vec4_f32 b, f32 t); // ================================================================ // NOTE: Dimensions are in pixels. typedef struct { f64 x; f64 y; f64 width; f64 height; } Box; typedef struct { i64 width; i64 height; i64 stride; u8 *pixels; } Stencil_Buffer; typedef struct { i64 width; i64 height; i64 stride; vec4_f32 *pixels; } Pixel_Buffer; enum { GRAPHICS_FILL_TRIANGLE, GRAPHICS_FILL_TRIANGLES, GRAPHICS_FILL_QUAD, GRAPHICS_FILL_ELLIPSE, GRAPHICS_FILL_LINE, GRAPHICS_DRAW_TEXT_AREA, GRAPHICS_DRAW_TEXT_CURSOR, }; typedef struct graphics_request_ { u16 op; union { struct { vec2 vertices[3]; } triangle; struct { vec2 position; vec2 scale; i64 num_triangles; vec2 *vertices; } triangles; struct { vec2 vertices[4]; } quad; struct { Box area; } ellipse; struct { vec2 vertices[2]; f64 width; } line; struct { i32 font; Box area; vec2 max_size; i64 num_chars; c32 *text; } text_area; struct { i32 font; Box area; vec2 max_size; i64 num_chars; c32 *text; i64 cursor; i64 selection; } text_cursor; }; } Graphics_Request; typedef struct { b8 disable_cache; vec4_f32 color; vec2 scale; Pixel_Buffer dst; } Graphics_Context; // ================================================================ // // User program interface // // ================================================================ Box dispatch_font_text_area(i32 font, vec2 scale, i64 num_chars, c32 *text); void dispatch_font_render_to_stencil(Stencil_Buffer dst, i32 font, vec2 position, vec2 scale, i64 num_chars, c32 *text); // ================================================================ vec3_f32 lab_from_rgb(vec3_f32 rgb); vec3_f32 rgb_from_lab(vec3_f32 lab); vec3_f32 lab_from_lch(vec3_f32 lch); vec3_f32 lch_from_lab(vec3_f32 lab); vec3_f32 rgb_from_lch(vec3_f32 lch); vec3_f32 lch_from_rgb(vec3_f32 rgb); // NOTE: General rule to work with gamma is removing it, // then doing computations like blending, interpolation, etc, // then adding it back. vec3_f32 rgb_gamma_add (vec3_f32 rgb); vec3_f32 rgb_gamma_remove (vec3_f32 rgb); vec4_f32 rgba_gamma_add (vec4_f32 rgb); vec4_f32 rgba_gamma_remove(vec4_f32 rgb); vec3_f32 rgb_gamma_lerp (vec3_f32 a, vec3_f32 b, f32 t); vec4_f32 rgba_gamma_lerp(vec4_f32 a, vec4_f32 b, f32 t); vec3_f32 rgb_mix (vec3_f32 a, vec3_f32 b, f32 t); vec4_f32 rgba_mix (vec4_f32 a, vec4_f32 b, f32 t); vec3_f32 lch_mix (vec3_f32 a, vec3_f32 b, f32 t); vec3_f32 lch_lerp (vec3_f32 a, vec3_f32 b, f32 t); u32 rgb_u32_from_f32(vec3_f32 color); vec3_f32 rgb_f32_from_u32(u32 color); u32 rgba_u32_from_f32(vec4_f32 color); vec4_f32 rgba_f32_from_u32(u32 color); b8 hit_rectangle(Box area, vec2 point); b8 hit_triangle (vec2 vertices[3], vec2 point); b8 hit_quad (vec2 vertices[4], vec2 point); b8 hit_ellipse (Box area, vec2 point); b8 hit_line (vec2 vertices[2], f64 width, vec2 point); Stencil_Buffer default_stencil(i64 width, i64 height); void fill_stencil_to_stencil (Stencil_Buffer dst, u8 value, Box area, Stencil_Buffer src); void fill_rectangle_to_stencil (Stencil_Buffer dst, u8 value, Box area); void fill_triangle_to_stencil (Stencil_Buffer dst, u8 value, vec2 vertices[3]); void fill_triangles_to_stencil (Stencil_Buffer dst, u8 value, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices); void fill_quad_to_stencil (Stencil_Buffer dst, u8 value, vec2 vertices[4]); void fill_ellipse_to_stencil (Stencil_Buffer dst, u8 value, Box area); void fill_line_to_stencil (Stencil_Buffer dst, u8 value, vec2 vertices[2], f64 width); void draw_text_area_to_stencil (Stencil_Buffer dst, u8 value, i32 font, Box area, vec2 max_size, i64 num_chars, c32 *text); void draw_text_cursor_to_stencil(Stencil_Buffer dst, u8 value, i32 font, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection); void draw_pixels_to_buffer (Pixel_Buffer dst, Box area, Pixel_Buffer src); void fill_stencil_to_buffer (Pixel_Buffer dst, vec4_f32 color, Box area, Stencil_Buffer src); void fill_rectangle_to_buffer (Pixel_Buffer dst, vec4_f32 color, Box area); void fill_triangle_to_buffer (Pixel_Buffer dst, vec4_f32 color, vec2 vertices[3], Stencil_Buffer stencil); void fill_triangles_to_buffer (Pixel_Buffer dst, vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices, Stencil_Buffer stencil); void fill_quad_to_buffer (Pixel_Buffer dst, vec4_f32 color, vec2 vertices[4], Stencil_Buffer stencil); void fill_ellipse_to_buffer (Pixel_Buffer dst, vec4_f32 color, Box area, Stencil_Buffer stencil); void fill_line_to_buffer (Pixel_Buffer dst, vec4_f32 color, vec2 vertices[2], f64 width, Stencil_Buffer stencil); void draw_text_area_to_buffer (Pixel_Buffer dst, vec4_f32 color, i32 font, Box area, vec2 max_size, i64 num_chars, c32 *text, Stencil_Buffer stencil); void draw_text_cursor_to_buffer(Pixel_Buffer dst, vec4_f32 color, i32 font, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection, Stencil_Buffer stencil); void fill_triangle_cached (vec4_f32 color, vec2 vertices[3]); void fill_triangles_cached (vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices); void fill_quad_cached (vec4_f32 color, vec2 vertices[4]); void fill_ellipse_cached (vec4_f32 color, Box area); void fill_line_cached (vec4_f32 color, vec2 vertices[2], f64 width); void draw_text_area_cached (i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *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); void draw_pixels (Box area, Pixel_Buffer src); void fill_rectangle (vec4_f32 color, Box area); void fill_triangle (vec4_f32 color, vec2 vertices[3]); void fill_triangles (vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices); void fill_quad (vec4_f32 color, vec2 vertices[4]); void fill_ellipse (vec4_f32 color, Box area); void fill_line (vec4_f32 color, vec2 vertices[2], f64 width); void draw_text_area (i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text); void draw_text_cursor(i32 font, vec4_f32 color, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection); void perform_graphics_request(Graphics_Context context, Graphics_Request req); void clean_graphics_requests_cache(i64 amount); #endif // GRAPHICS_HEADER_GUARD_ // ================================================================ // // IMPLEMENTATION // // ================================================================ #ifndef GRAPHICS_HEADER #ifndef GRAPHICS_IMPL_GUARD_ #define GRAPHICS_IMPL_GUARD_ // ================================================================ // // Colors // // ================================================================ vec3_f32 vec3_from_vec4_f32(vec4_f32 v) { return (vec3_f32) { .x = v.x, .y = v.y, .z = v.z, }; } 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, }; } 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, }; } 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, }; } vec3_f32 rgb_gamma_add(vec3_f32 rgb) { return (vec3_f32) { .x = gamma_(rgb.x), .y = gamma_(rgb.y), .z = gamma_(rgb.z), }; } 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), }; } 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), }; } 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), }; } 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; f64 l_ = cbrt(l); f64 m_ = cbrt(m); f64 s_ = cbrt(s); 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_), }; } 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; f64 l = l_ * l_ * l_; f64 m = m_ * m_ * m_; f64 s = s_ * s_ * s_; 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), }; } vec3_f32 lch_from_lab(vec3_f32 lab) { f64 a = lab.y; f64 b = lab.z; return (vec3_f32) { .x = lab.x, .y = (f32) sqrt (a * a + b * b), .z = (f32) atan2(b, a), }; } 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)), }; } vec3_f32 rgb_from_lch(vec3_f32 lch) { return rgb_from_lab(lab_from_lch(lch)); } vec3_f32 lch_from_rgb(vec3_f32 rgb) { return lch_from_lab(lab_from_rgb(rgb)); } 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 )); } 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 )); } 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); 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, }); } 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); } 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, }; } 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.0; if (delta_hue < -M_PI) delta_hue += M_PI * 2.0; 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, }; } u32 rgb_u32_from_f32(vec3_f32 color) { i32 ir = (i32) floor(color.x * 255.0 + 0.5); i32 ig = (i32) floor(color.y * 255.0 + 0.5); i32 ib = (i32) floor(color.z * 255.0 + 0.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; return (r << 16) | (g << 8) | b; } vec3_f32 rgb_f32_from_u32(u32 color) { return (vec3_f32) { .x = ((color & 0xff0000) >> 16) / 255.0f, .y = ((color & 0x00ff00) >> 8) / 255.0f, .z = (color & 0x0000ff) / 255.0f, }; } u32 rgba_u32_from_f32(vec4_f32 color) { i32 ir = (i32) floor(color.x * 255.0 + 0.5); i32 ig = (i32) floor(color.y * 255.0 + 0.5); i32 ib = (i32) floor(color.z * 255.0 + 0.5); i32 ia = (i32) floor(color.w * 255.0 + 0.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.0f, .y = ((color & 0x0000ff00) >> 8) / 255.0f, .z = (color & 0x000000ff) / 255.0f, .w = ((color & 0xff000000) >> 24) / 255.0f, }; } // ================================================================ // // 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 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; // Z-components of cross-products f64 z0 = x02 * y10 - x10 * y02; f64 z1 = x10 * y21 - x21 * y10; f64 z2 = x21 * y02 - x02 * y21; f64 pz0 = x02 * (py - y0) - (px - x0) * y02; f64 pz1 = x10 * (py - y1) - (px - x1) * y10; f64 pz2 = x21 * (py - y2) - (px - x2) * y21; return same_sign_(z0, pz0) && same_sign_(z1, pz1) && same_sign_(z2, pz2); } 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; return hit_triangle((vec2[3]) { { x0, y0 }, { x1, y1 }, { x2, y2 }, }, point) || hit_triangle((vec2[3]) { { x0, y0 }, { x2, y2 }, { x3, y3 }, }, point); } b8 hit_ellipse(Box area, vec2 point) { f64 dw = area.width / 2; f64 dh = area.height / 2; if (dw < EPSILON || dh < EPSILON) return 0; f64 cx = area.x + dw; f64 cy = area.y + dh; f64 kx = 1. / dw; f64 ky = 1. / dh; f64 dx = (point.x - cx) * kx; f64 dy = (point.y - cy) * ky; return dx * dx + dy * dy - 1.0 < EPSILON; } 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; // Tangent // f64 tx = -dy; f64 ty = dx; f64 tl = sqrt(tx * tx + ty * ty); if (tl >= EPSILON) { tx /= tl; ty /= tl; } tx *= width * 0.5; ty *= width * 0.5; return hit_quad((vec2[4]) { { x0 - tx, y0 - ty }, { x0 + tx, y0 + ty }, { x1 + tx, y1 + ty }, { x1 - tx, y1 - ty }, }, point); } static u8 mix_u8_(u8 x, u8 y) { u32 z = (((u32) x) * ((u32) y) + 0x7f) / 0xff; return z >= 0xff ? (u8) 0xff : (u8) z; } static i64 _default_stencil_len = 0; static u8 *_default_stencil = NULL; Stencil_Buffer default_stencil(i64 width, i64 height) { i64 len = width * height; if (len < 0) len = 1; if (len > _default_stencil_len) resize_dynamic_array_exact(&_default_stencil_len, (void **) &_default_stencil, sizeof *_default_stencil, len); if (width <= 0 || height <= 0) return (Stencil_Buffer) { .width = 0, .height = 0, .stride = 1, .pixels = _default_stencil, }; i64 h = height; if (len > _default_stencil_len) h = _default_stencil_len / width; mem_set_(_default_stencil, 0, width * h); return (Stencil_Buffer) { .width = width, .height = h, .stride = width, .pixels = _default_stencil, }; } void fill_stencil_to_stencil(Stencil_Buffer dst, u8 value, Box area, Stencil_Buffer src) { i64 x0 = (i64) floor(area.x + 0.5); i64 x1 = (i64) floor(area.x + area.width + 0.5); i64 y0 = (i64) floor(area.y); i64 y1 = (i64) floor(area.y + area.height + 0.5); i64 i0 = max2_i64_(x0, 0); i64 i1 = min2_i64_(x1, dst.width); i64 j0 = max2_i64_(y0, 0); i64 j1 = min2_i64_(y1, dst.height); if (i1 <= i0 || j1 <= j0) return; u32 x_ratio = (src.width << 16) / (x1 - x0); u32 y_ratio = (src.height << 16) / (y1 - y0); u32 x_half = x_ratio / 2; u32 y_half = y_ratio / 2; for (i64 j = j0; j < j1; ++j) { u32 jj = ((j - y0) * y_ratio + y_half) >> 16; u8 *s = src.pixels + jj * src.stride; u8 *d = dst.pixels + j * dst.stride; u8 *d_end = d + (i1 - i0); for (u32 x = i0 - x0; d < d_end; ++d, ++x) *d = max2_u8_(*d, mix_u8_(value, s[(x * x_ratio + x_half) >> 16])); } } void fill_rectangle_to_stencil(Stencil_Buffer dst, u8 value, Box area) { i64 x0 = (i64) floor(area.x + 0.5); i64 x1 = (i64) floor(area.x + area.width + 0.5); i64 y0 = (i64) floor(area.y); i64 y1 = (i64) floor(area.y + area.height + 0.5); i64 i0 = max2_i64_(x0, 0); i64 i1 = min2_i64_(x1, dst.width); i64 j0 = max2_i64_(y0, 0); i64 j1 = min2_i64_(y1, dst.height); for (i64 j = j0; j < j1; ++j) { u8 *d = dst.pixels + j * dst.stride + i0; u8 *d_end = d + (i1 - i0); for (; d < d_end; ++d) *d = max2_u8_(*d, value); } } void fill_triangle_to_stencil(Stencil_Buffer dst, u8 value, vec2 vertices[3]) { i64 x0 = (i64) floor(vertices[0].x + 0.5); i64 y0 = (i64) floor(vertices[0].y + 0.5); i64 x1 = (i64) floor(vertices[1].x + 0.5); i64 y1 = (i64) floor(vertices[1].y + 0.5); i64 x2 = (i64) floor(vertices[2].x + 0.5); i64 y2 = (i64) floor(vertices[2].y + 0.5); i64 i0 = max2_i64_(min3_i64_(x0, x1, x2), 0); i64 j0 = max2_i64_(min3_i64_(y0, y1, y2), 0); i64 i1 = min2_i64_(max3_i64_(x0, x1, x2) + 1, dst.width); i64 j1 = min2_i64_(max3_i64_(y0, y1, y2) + 1, dst.height); for (i64 j = j0; j < j1; ++j) { // FIXME, PERF: Calculate the exact intersection. i64 left = i1 - 1; i64 right = i0; for (i64 i = i0; i < i1; ++i) if (hit_triangle(vertices, (vec2) { (f64) i, (f64) j, })) { left = i; break; } for (i64 i = i1 - 1; i >= i0; --i) if (hit_triangle(vertices, (vec2) { (f64) i, (f64) j, })) { right = i; break; } u8 *d = dst.pixels + j * dst.stride + left; u8 *d_end = dst.pixels + j * dst.stride + right + 1; for (; d < d_end; ++d) *d = max2_u8_(*d, value); } } void fill_triangles_to_stencil(Stencil_Buffer dst, u8 value, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices) { if (vertices == NULL) { LOG_error("Sanity"); return; } // NOTE: Stencil filling is additive. for (i64 i = 0; i < num_triangles; ++i) fill_triangle_to_stencil(dst, value, (vec2[3]) { { position.x + vertices[i * 3].x * scale.x, position.y + vertices[i * 3].y * scale.y }, { position.x + vertices[i * 3 + 1].x * scale.x, position.y + vertices[i * 3 + 1].y * scale.y }, { position.x + vertices[i * 3 + 2].x * scale.x, position.y + vertices[i * 3 + 2].y * scale.y }, }); } void fill_quad_to_stencil(Stencil_Buffer dst, u8 value, vec2 vertices[4]) { i64 x0 = (i64) floor(vertices[0].x + 0.5); i64 y0 = (i64) floor(vertices[0].y + 0.5); i64 x1 = (i64) floor(vertices[1].x + 0.5); i64 y1 = (i64) floor(vertices[1].y + 0.5); i64 x2 = (i64) floor(vertices[2].x + 0.5); i64 y2 = (i64) floor(vertices[2].y + 0.5); i64 x3 = (i64) floor(vertices[3].x + 0.5); i64 y3 = (i64) floor(vertices[3].y + 0.5); i64 i0 = max2_i64_(min4_i64_(x0, x1, x2, x3), 0); i64 j0 = max2_i64_(min4_i64_(y0, y1, y2, y3), 0); i64 i1 = min2_i64_(max4_i64_(x0, x1, x2, x3) + 1, dst.width); i64 j1 = min2_i64_(max4_i64_(y0, y1, y2, y3) + 1, dst.height); for (i64 j = j0; j < j1; ++j) { // FIXME, PERF: Calculate the exact intersection. i64 left = i1 - 1; i64 right = i0; for (i64 i = i0; i < i1; ++i) if (hit_quad(vertices, (vec2) { (f64) i, (f64) j, })) { left = i; break; } for (i64 i = i1 - 1; i >= i0; --i) if (hit_quad(vertices, (vec2) { (f64) i, (f64) j, })) { right = i; break; } u8 *d = dst.pixels + j * dst.stride + left; u8 *d_end = dst.pixels + j * dst.stride + right + 1; for (; d < d_end; ++d) *d = max2_u8_(*d, value); } } void fill_ellipse_to_stencil(Stencil_Buffer dst, u8 value, Box area) { f64 y1 = area.y + area.height; i64 j0 = (i64) floor(area.y + 0.5); i64 j1 = (i64) floor(y1 + 0.5); if (j0 < 0) j0 = 0; if (j1 > dst.height) j1 = dst.height; f64 half_w = area.width / 2.0; f64 half_h = area.height / 2.0; if (half_h < EPSILON) return; f64 inv_half_h = 1.0 / half_h; f64 cx = area.x + half_w; f64 cy = area.y + half_h; 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 dx = half_w * sqrt(1.0 - ry * ry); i64 left = (i64) floor(cx - dx + 0.5); i64 right = (i64) floor(cx + dx - 0.5); if (left < 0) left = 0; if (right >= dst.width) right = dst.width - 1; u8 *d = dst.pixels + j * dst.stride + left; u8 *d_end = dst.pixels + j * dst.stride + right + 1; for (; d < d_end; ++d) *d = max2_u8_(*d, value); } } 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; f64 dx = x1 - x0; f64 dy = y1 - y0; // Tangent // f64 tx = -dy; f64 ty = dx; f64 tl = sqrt(tx * tx + ty * ty); if (tl < EPSILON) return 0; f64 k = 0.5 / tl; tx *= width * k; ty *= width * k; 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; } void fill_line_to_stencil(Stencil_Buffer dst, u8 value, vec2 vertices[2], f64 width) { // FIXME, PERF: Use Bresenham's algorithm when width is 1. if (width < EPSILON) return; vec2 quad[4]; if (!quad_from_line_(quad, vertices, width)) return; fill_quad_to_stencil(dst, value, quad); if (width > 1.0) { f64 half_w = width * 0.5; fill_ellipse_to_stencil(dst, value, (Box) { .x = vertices[0].x - half_w, .y = vertices[0].y - half_w, .width = width, .height = width, }); fill_ellipse_to_stencil(dst, value, (Box) { .x = vertices[1].x - half_w, .y = vertices[1].y - half_w, .width = width, .height = width, }); } } 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 = (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 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 x0 = (i64) floor(area.x + 0.5); i64 x1 = (i64) floor(area.x + area.width + 0.5); i64 y0 = (i64) floor(area.y + 0.5); i64 y1 = (i64) floor(area.y + area.height + 0.5); i64 i0 = max2_i64_(x0, 0); i64 i1 = min2_i64_(x1, dst.width); i64 j0 = max2_i64_(y0, 0); i64 j1 = min2_i64_(y1, dst.height); u32 x_ratio = (src.width << 16) / (x1 - x0); u32 y_ratio = (src.height << 16) / (y1 - y0); u32 x_half = x_ratio / 2; u32 y_half = y_ratio / 2; for (i64 j = j0; j < j1; ++j) { u32 src_j = ((j - y0) * y_ratio + y_half) >> 16; vec4_f32 *d = dst.pixels + j * dst.stride + i0; vec4_f32 *d_end = d + (i1 - i0); vec4_f32 *s = src.pixels + src_j * src.stride; for (u32 i = i0 - x0; d < d_end; ++d, ++i) { u32 src_i = (i * x_ratio + x_half) >> 16; put_pixel_(d, s[src_i]); } } PROFILER_end(PROFILE_DRAW_PIXELS); } static vec4_f32 mix_u8_color_(u8 value, vec4_f32 color) { return (vec4_f32) { color.x, color.y, color.z, (color.w * value) / 255.0, }; } void fill_stencil_to_buffer(Pixel_Buffer dst, vec4_f32 color, Box area, Stencil_Buffer src) { if (area.width < EPSILON || area.height < EPSILON) return; i64 x0 = (i64) floor(area.x + 0.5); i64 x1 = (i64) floor(area.x + area.width + 0.5); i64 y0 = (i64) floor(area.y + 0.5); i64 y1 = (i64) floor(area.y + area.height + 0.5); i64 i0 = max2_i64_(x0, 0); i64 i1 = min2_i64_(x1, dst.width); i64 j0 = max2_i64_(y0, 0); i64 j1 = min2_i64_(y1, dst.height); u32 x_ratio = (src.width << 16) / (x1 - x0); u32 y_ratio = (src.height << 16) / (y1 - y0); u32 x_half = x_ratio / 2; u32 y_half = y_ratio / 2; for (i64 j = j0; j < j1; ++j) { u32 src_j = ((j - y0) * y_ratio + y_half) >> 16; vec4_f32 *d = dst.pixels + j * dst.stride + i0; vec4_f32 *d_end = d + (i1 - i0); u8 *s = src.pixels + src_j * src.stride; for (u32 i = i0 - x0; d < d_end; ++d, ++i) { u32 src_i = (i * x_ratio + x_half) >> 16; u8 value = s[src_i]; if (value == 0) continue; put_pixel_(d, mix_u8_color_(value, color)); } } } void fill_rectangle_to_buffer(Pixel_Buffer dst, vec4_f32 color, Box area) { PROFILER_begin(PROFILE_FILL_RECTANGLE); 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); if (i0 < 0) i0 = 0; if (i1 > dst.width) i1 = dst.width; if (j0 < 0) j0 = 0; if (j1 > dst.height) j1 = dst.height; for (i64 j = j0; j < j1; ++j) { vec4_f32 *p = dst.pixels + j * dst.stride + i0; vec4_f32 *p_end = p + (i1 - i0); for (; p < p_end; ++p) put_pixel_(p, color); } PROFILER_end(PROFILE_FILL_RECTANGLE); } void fill_triangle_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[3], Stencil_Buffer stencil) { PROFILER_begin(PROFILE_FILL_TRIANGLE); f64 x0 = floor(min3_(vertices[0].x, vertices[1].x, vertices[2].x)); f64 y0 = floor(min3_(vertices[0].y, vertices[1].y, vertices[2].y)); fill_triangle_to_stencil( stencil, 255, (vec2[3]) { { vertices[0].x - x0, vertices[0].y - y0 }, { vertices[1].x - x0, vertices[1].y - y0 }, { vertices[2].x - x0, vertices[2].y - y0 }, } ); Box area = { .x = x0, .y = y0, .width = stencil.width, .height = stencil.height, }; fill_stencil_to_buffer(dst, color, area, stencil); PROFILER_end(PROFILE_FILL_TRIANGLE); } static void triangles_origin_(f64 *x0, f64 *y0, vec2 p, vec2 s, i64 num, vec2 *v) { if (num <= 0) return; *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); 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); } } void fill_triangles_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 position, vec2 scale, i64 num_triangles, vec2 *vertices, Stencil_Buffer stencil) { PROFILER_begin(PROFILE_FILL_TRIANGLES); f64 x0 = 0.0, y0 = 0.0; triangles_origin_(&x0, &y0, position, scale, num_triangles, vertices); x0 = floor(x0); y0 = floor(y0); fill_triangles_to_stencil( stencil, 255, (vec2) { position.x - x0, position.y - y0 }, scale, num_triangles, vertices ); Box area = { .x = x0, .y = y0, .width = stencil.width, .height = stencil.height, }; fill_stencil_to_buffer(dst, color, area, stencil); PROFILER_end(PROFILE_FILL_TRIANGLES); } void fill_quad_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[4], Stencil_Buffer stencil) { PROFILER_begin(PROFILE_FILL_QUAD); f64 x0 = floor(min4_(vertices[0].x, vertices[1].x, vertices[2].x, vertices[3].x)); f64 y0 = floor(min4_(vertices[0].y, vertices[1].y, vertices[2].y, vertices[3].y)); fill_quad_to_stencil( stencil, 255, (vec2[4]) { { vertices[0].x - x0, vertices[0].y - y0 }, { vertices[1].x - x0, vertices[1].y - y0 }, { vertices[2].x - x0, vertices[2].y - y0 }, { vertices[3].x - x0, vertices[3].y - y0 }, } ); Box area = { .x = x0, .y = y0, .width = stencil.width, .height = stencil.height, }; fill_stencil_to_buffer(dst, color, area, stencil); PROFILER_end(PROFILE_FILL_QUAD); } void fill_ellipse_to_buffer(Pixel_Buffer dst, vec4_f32 color, Box area, Stencil_Buffer stencil) { PROFILER_begin(PROFILE_FILL_ELLIPSE); f64 x0 = floor(area.x); f64 y0 = floor(area.y); fill_ellipse_to_stencil( stencil, 255, (Box) { .x = area.x - x0, .y = area.y - y0, .width = area.width, .height = area.height, } ); Box dst_area = { .x = x0, .y = y0, .width = stencil.width, .height = stencil.height, }; fill_stencil_to_buffer(dst, color, dst_area, stencil); PROFILER_end(PROFILE_FILL_ELLIPSE); } void fill_line_to_buffer(Pixel_Buffer dst, vec4_f32 color, vec2 vertices[2], f64 width, Stencil_Buffer stencil) { PROFILER_begin(PROFILE_FILL_LINE); f64 x0 = floor(min2_(vertices[0].x, vertices[1].x) - width); f64 y0 = floor(min2_(vertices[0].y, vertices[1].y) - width); fill_line_to_stencil( stencil, 255, (vec2[2]) { { vertices[0].x - x0, vertices[0].y - y0 }, { vertices[1].x - x0, vertices[1].y - y0 }, }, width ); Box area = { .x = x0, .y = y0, .width = stencil.width, .height = stencil.height, }; fill_stencil_to_buffer(dst, color, area, stencil); PROFILER_end(PROFILE_FILL_LINE); } // ================================================================ // // LCD text rendering // // ================================================================ enum { LCD_Width = 6, LCD_Height = 7, LCD_First = 33, LCD_Space = 4, LCD_Len = 94, }; static u64 _lcd_bits[LCD_Len] = { 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, }; u8 _lcd_widths[LCD_Len] = { 1, 3, 5, 5, 5, 4, 1, 2, 2, 6, 5, 2, 5, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 5, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 4, 3, 5, 5, 4, 4, 4, 4, 4, 5, 4, 5, 5, 5, 5, 4, 2, 3, 2, 5, 6, 2, 4, 4, 4, 4, 4, 3, 4, 4, 3, 2, 3, 2, 5, 4, 4, 4, 4, 3, 4, 3, 4, 5, 5, 5, 5, 4, 3, 1, 3, 6, }; u64 _lcd_kerning[LCD_Len * 2] = { 0xffffffffffffffffull, 0x3fffffffull, 0xbffffffff3ff83ffull, 0x3bfffcd6ull, 0x7ff7ffffffffffffull, 0x3fffffffull, 0x7f7ffffbbbff4b5ull, 0xff3ffffull, 0x77f7ffffbfbfffbdull, 0x1ff7ffffull, 0x57d7ffffb7b7e9a5ull, 0x1bd2ff9full, 0xbffffffff3ff83ffull, 0x3bfffcd6ull, 0xdfffffffe7ffe9f7ull, 0x3bfabf82ull, 0xffffffffffffffffull, 0x3fffffffull, 0x2d77fcfffbff96ffull, 0x2fffffffull, 0x26f7feff9bba96bdull, 0xe77ffffull, 0x57d7ffffb7b7e9a5ull, 0x1bd2ff9full, 0x26f7feff9bba96bdull, 0xe77ffffull, 0x57d7ffffb7b7e9a5ull, 0x1bd2ff9full, 0xbffffffff3ff83ffull, 0x3bfffcd6ull, 0xffffffffffffffffull, 0x3fffffffull, 0x57d7ffffb7b7e9a5ull, 0x1bd2ff9full, 0xffffffffffffffffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xbffffffffbff97ffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0x7ff7ffffffffffffull, 0x3fffffffull, 0x7ff7ffffffffffffull, 0x3fffffffull, 0xfffffffff7ffebffull, 0x3bffffdfull, 0x2ff7fffffbfff6ffull, 0x2fffffffull, 0x26f7feff9bba96bdull, 0xe77ffffull, 0xbffffffffbff97ffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xdffffffff7ffe9f7ull, 0x3bfaff9full, 0x9fffffffe1df81f3ull, 0x3bfabc82ull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xdffffffff7ffe9f7ull, 0x3bfaff9full, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xdffffffffffffdf7ull, 0x3ffbffffull, 0x57d7ffffb7b7e9a5ull, 0x1bd2ff9full, 0xffffffffffffffffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xbffffffffbff97ffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0x27f7ffffbbbff6bdull, 0xff7ffffull, 0x9fffffffe1df81f3ull, 0x3bfabc82ull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xbffffffffbff97ffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xdffffffffffffdf7ull, 0x3ffbffffull, 0xbffffffff3ff83ffull, 0x3bfffcd6ull, 0xfffffffff7ffebffull, 0x3bffffdfull, 0xdfffffffe7ffe9f7ull, 0x3bfabf82ull, 0x57d7ffffb7b7e9a5ull, 0x1bd2ff9full, 0xffffffffffffffffull, 0x3fffffffull, 0x2d77fcfffbff96ffull, 0x2fffffffull, 0x5782bdbba7b7e9a5ull, 0x1b82b782ull, 0xbffffffff3ff83ffull, 0x3bfffcd6ull, 0x2ff7fffffbfff6ffull, 0x2fffffffull, 0x27f7ffffbbbff6bdull, 0xff7ffffull, 0x2ff7fffffbfff6ffull, 0x2fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0x2ff7fefffbff96ffull, 0x2fffffffull, 0xbffffffffbff97ffull, 0x3fffffffull, 0x7ff7ffffffffffffull, 0x3fffffffull, 0x57f7ffffbfbffdb5ull, 0x1ff3ffffull, 0x57d7ffffb7b7e9a5ull, 0x1bd2ff9full, 0xffffffffffffffffull, 0x3fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0x57d7ffffb7b7e9a5ull, 0x1bd2ff9full, 0x7ff7ffffffffffffull, 0x3fffffffull, 0x7ff7ffffffffffffull, 0x3fffffffull, 0x2ff7fffffbfff6ffull, 0x2fffffffull, 0x2ff7fefffbff96ffull, 0x2fffffffull, 0xffffffffffffffffull, 0x3fffffffull, 0xbffffffff3ff83ffull, 0x3bfffcd6ull, 0x7f7ffffbbbff4b5ull, 0xff3ffffull, 0xbffffffff3ff83ffull, 0x3bfffcd6ull, 0xffffffffffffffffull, 0x3fffffffull, 0xbffffffffbff97ffull, 0x3fffffffull, 0xbffffffffbfff7ffull, 0x3fffffffull, 0xfffffffff7ffebffull, 0x3bffffdfull, 0xbffffffff3ff83ffull, 0x3bfffcd6ull, 0xffffffffffffffffull, 0x3fffffffull, 0xdfffffffe7ffe9f7ull, 0x3bfabf82ull, 0xffffffffffffffffull, 0x3fffffffull, 0x26f7feff9bba96bdull, 0xe77ffffull, 0x9fffffffe1df81f3ull, 0x3bfabc82ull, }; static i64 lcd_char_width_(c32 c) { i64 index = c - LCD_First; if (index < -1) return 0; if (index < 0 || index >= LCD_Len) return LCD_Space; return _lcd_widths[index]; } static i64 lcd_spacing_(i64 num_chars, c32 *text, i64 index) { if (text == NULL) return 0; if (index < 0 || index + 1 >= num_chars) return 0; i64 a = text[index] - LCD_First; i64 b = text[index + 1] - LCD_First; if (a < 0 || a >= LCD_Len || b < 0 || b >= LCD_Len) return 0; if (b < 64) return !!(_lcd_kerning[a * 2] & (1ull << b )); return !!(_lcd_kerning[a * 2 + 1] & (1ull << (b - 64))); } static i64 lcd_cursor_(i64 num_chars, c32 *text) { if (text == NULL) return 0; i64 cursor = 0; 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); } return cursor; } static i64 lcd_enum_text_columns_(i64 num_chars, c32 *text) { if (text == NULL) return 0; i64 cols = 0; i64 n = 0; 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); } if (cols < n) cols = n; return cols; } static i64 lcd_enum_text_rows_(i64 num_chars, c32 *text) { if (text == NULL) return 0; i64 rows = 0; for (i64 i = 0; i <= num_chars; ++i) if (i == num_chars || text[i] == '\n') { if (rows > 0) ++rows; rows += LCD_Height; } return rows; } static void lcd_draw_text_(Stencil_Buffer dst, u8 value, 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; } i64 num_cols = lcd_char_width_(text[n]); f64 w = num_cols * kx; i64 index = text[n] - LCD_First; 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 i00 = i0; i64 j00 = j0; if (i0 < 0) i0 = 0; if (i1 > dst.width) i1 = dst.width; if (j0 < 0) j0 = 0; if (j1 > dst.height) j1 = dst.height; u32 w_inv = (u32) floor(0x10000 / w + 0.5); u32 h_inv = (u32) floor(0x10000 / h + 0.5); u32 w_half = w_inv / 2; u32 h_half = h_inv / 2; for (i64 j = j0; j < j1; ++j) { u32 row = (((j - j00) * LCD_Height) * h_inv + h_half) >> 16; for (i64 i = i0; i < i1; ++i) { u32 column = (((i - i00) * num_cols) * w_inv + w_half) >> 16; if ((_lcd_bits[index] & (1ull << (row * 8 + column))) != 0) dst.pixels[j * dst.stride + i] = max2_u8_(dst.pixels[j * dst.stride + i], value); } } } x += kx * (num_cols + lcd_spacing_(num_chars, text, n)); } } #if !defined(ENABLE_FONT_CUSTOM_DISPATCH) Box dispatch_font_text_area(i32 font, vec2 scale, i64 num_chars, c32 *text) { // TODO (void) font; (void) scale; (void) num_chars; (void) text; return (Box) {0}; } void dispatch_font_render_to_stencil(Stencil_Buffer dst, i32 font, vec2 position, vec2 scale, i64 num_chars, c32 *text) { // TODO (void) dst; (void) font; (void) position; (void) scale; (void) num_chars; (void) text; } #endif // !defined(ENABLE_FONT_CUSTOM_DISPATCH) void draw_text_area_to_stencil(Stencil_Buffer dst, u8 value, i32 font, Box area, vec2 max_size, i64 num_chars, c32 *text) { (void) font; if (text == NULL || num_chars <= 0 || max_size.x < EPSILON || max_size.y < EPSILON) return; i64 num_columns = lcd_enum_text_columns_(num_chars, text); i64 num_rows = lcd_enum_text_rows_(num_chars, text); f64 scale_x = area.width / num_columns; f64 scale_y = area.height / num_rows; f64 kx = scale_x / max_size.x; f64 ky = scale_y / max_size.y; f64 k = kx < ky ? kx : ky; kx = k * max_size.x; ky = k * max_size.y; lcd_draw_text_(dst, value, area.x, area.y, kx, ky, num_chars, text); } void draw_text_cursor_to_stencil(Stencil_Buffer dst, u8 value, i32 font, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection) { (void) font; if (max_size.x < EPSILON || max_size.y < EPSILON) return; 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; f64 kx = scale_x / max_size.x; f64 ky = scale_y / max_size.y; f64 k = kx < ky ? kx : ky; kx = k * max_size.x; ky = k * max_size.y; if (selection != 0) { i64 selection_x, selection_y; 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 (cursor_y == selection_y) fill_rectangle_to_stencil( dst, value, (Box) { .x = area.x + kx * cursor_x, .y = area.y + ky * cursor_y - ky * LCD_Height, .width = kx * (selection_x - cursor_x), .height = ky * (LCD_Height + 1), } ); else { fill_rectangle_to_stencil( dst, value, (Box) { .x = area.x + kx * cursor_x, .y = area.y + ky * cursor_y - ky * LCD_Height, .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_stencil( dst, value, (Box) { .x = area.x, .y = area.y + ky * j - ky * LCD_Height, .width = kx * num_columns, .height = ky * (LCD_Height + 1), } ); fill_rectangle_to_stencil( dst, value, (Box) { .x = area.x, .y = area.y + ky * selection_y - ky * LCD_Height, .width = kx * selection_x, .height = ky * (LCD_Height + 1), } ); } } else fill_rectangle_to_stencil( dst, value, (Box) { .x = area.x + kx * cursor_x, .y = area.y + ky * cursor_y - ky * LCD_Height, .width = kx * 0.5, .height = ky * (LCD_Height - 1), } ); } void draw_text_area_to_buffer(Pixel_Buffer dst, vec4_f32 color, i32 font, Box area, vec2 max_size, i64 num_chars, c32 *text, Stencil_Buffer stencil) { PROFILER_begin(PROFILE_DRAW_TEXT_AREA); f64 x0 = floor(area.x); f64 y0 = floor(area.y); draw_text_area_to_stencil( stencil, 255, font, (Box) { .x = area.x - x0, .y = area.y - y0, .width = area.width, .height = area.height, }, max_size, num_chars, text ); Box dst_area = { .x = x0, .y = y0, .width = stencil.width, .height = stencil.height, }; fill_stencil_to_buffer(dst, color, dst_area, stencil); PROFILER_end(PROFILE_DRAW_TEXT_AREA); } void draw_text_cursor_to_buffer(Pixel_Buffer dst, vec4_f32 color, i32 font, Box area, vec2 max_size, i64 num_chars, c32 *text, i64 cursor, i64 selection, Stencil_Buffer stencil) { PROFILER_begin(PROFILE_DRAW_TEXT_CURSOR); f64 x0 = floor(area.x); f64 y0 = floor(area.y); draw_text_cursor_to_stencil( stencil, 255, font, (Box) { .x = area.x - x0, .y = area.y - y0, .width = area.width, .height = area.height, }, max_size, num_chars, text, cursor, selection ); Box dst_area = { .x = x0, .y = y0, .width = stencil.width, .height = stencil.height, }; fill_stencil_to_buffer(dst, color, dst_area, stencil); PROFILER_end(PROFILE_DRAW_TEXT_CURSOR); } // ================================================================ // // Requests cache // // ================================================================ typedef struct { Graphics_Request req; i64 x; i64 y; i64 width; i64 height; } Graphics_Request_Norm_; typedef union { u8 hash[BLAKE2B_OUTBYTES]; u64 index; } Graphics_Request_Id_; typedef struct { b8 occupied; Graphics_Request_Id_ id; Graphics_Request req; i64 width; i64 height; i64 buffer_len; u8 * buffer; } Graphics_Cache_Slot_; static Graphics_Cache_Slot_ _graphics_cache[GRAPHICS_CACHE_SIZE] = {0}; static i64 _graphics_cache_gc_index = 0; static void graphics_request_hash_(Blake2b_State *S, Graphics_Request req) { switch (req.op) { case GRAPHICS_FILL_TRIANGLES: blake2b_update(S, (u8 *) &req.triangles.position, sizeof req.triangles.position); blake2b_update(S, (u8 *) &req.triangles.scale, sizeof req.triangles.scale); blake2b_update(S, (u8 *) &req.triangles.num_triangles, sizeof req.triangles.num_triangles); blake2b_update(S, (u8 *) req.triangles.vertices, req.triangles.num_triangles * 3 * sizeof *req.triangles.vertices); break; case GRAPHICS_DRAW_TEXT_AREA: blake2b_update(S, (u8 *) &req.text_area.font, sizeof req.text_area.font); blake2b_update(S, (u8 *) &req.text_area.area, sizeof req.text_area.area); blake2b_update(S, (u8 *) &req.text_area.max_size, sizeof req.text_area.max_size); blake2b_update(S, (u8 *) &req.text_area.num_chars, sizeof req.text_area.num_chars); blake2b_update( S, (u8 *) req.text_area.text, req.text_area.num_chars * sizeof *req.text_area.text ); break; case GRAPHICS_DRAW_TEXT_CURSOR: blake2b_update(S, (u8 *) &req.text_cursor.font, sizeof req.text_cursor.font); blake2b_update(S, (u8 *) &req.text_cursor.area, sizeof req.text_cursor.area); blake2b_update(S, (u8 *) &req.text_cursor.max_size, sizeof req.text_cursor.max_size); blake2b_update(S, (u8 *) &req.text_cursor.num_chars, sizeof req.text_cursor.num_chars); blake2b_update(S, (u8 *) req.text_cursor.text, req.text_cursor.num_chars * sizeof *req.text_cursor.text); blake2b_update(S, (u8 *) &req.text_cursor.cursor, sizeof req.text_cursor.cursor); blake2b_update( S, (u8 *) &req.text_cursor.selection, sizeof req.text_cursor.selection ); break; default: blake2b_update(S, (u8 *) &req, sizeof req); } } static Graphics_Request_Id_ graphics_request_id_(Graphics_Request req) { Blake2b_State S[1]; blake2b_init(S, BLAKE2B_OUTBYTES); graphics_request_hash_(S, req); Graphics_Request_Id_ id = {0}; blake2b_final(S, id.hash, BLAKE2B_OUTBYTES); return id; } static Graphics_Request graphics_request_scaled_(Graphics_Request req, vec2 scale) { Graphics_Request scaled = req; switch (req.op) { case GRAPHICS_FILL_TRIANGLE: scaled.triangle.vertices[0].x *= scale.x; scaled.triangle.vertices[0].y *= scale.y; scaled.triangle.vertices[1].x *= scale.x; scaled.triangle.vertices[1].y *= scale.y; scaled.triangle.vertices[2].x *= scale.x; scaled.triangle.vertices[2].y *= scale.y; break; case GRAPHICS_FILL_TRIANGLES: scaled.triangles.position.x *= scale.x; scaled.triangles.position.y *= scale.y; scaled.triangles.scale.x *= scale.x; scaled.triangles.scale.y *= scale.y; break; case GRAPHICS_FILL_QUAD: scaled.quad.vertices[0].x *= scale.x; scaled.quad.vertices[0].y *= scale.y; scaled.quad.vertices[1].x *= scale.x; scaled.quad.vertices[1].y *= scale.y; scaled.quad.vertices[2].x *= scale.x; scaled.quad.vertices[2].y *= scale.y; scaled.quad.vertices[3].x *= scale.x; scaled.quad.vertices[3].y *= scale.y; break; case GRAPHICS_FILL_ELLIPSE: scaled.ellipse.area.x *= scale.x; scaled.ellipse.area.y *= scale.y; scaled.ellipse.area.width *= scale.x; scaled.ellipse.area.height *= scale.y; break; case GRAPHICS_FILL_LINE: scaled.line.vertices[0].x *= scale.x; scaled.line.vertices[0].y *= scale.y; scaled.line.vertices[1].x *= scale.x; scaled.line.vertices[1].y *= scale.y; f64 dx = req.line.vertices[1].x - req.line.vertices[0].x; f64 dy = req.line.vertices[1].y - req.line.vertices[0].y; f64 l = sqrt(dx * dx + dy * dy); if (l > EPSILON) { dx /= l; dy /= l; f64 tx = dy * scale.x; f64 ty = -dx * scale.y; scaled.line.width *= sqrt(tx * tx + ty * ty); } else scaled.line.width *= (scale.x + scale.y) / 2.0; break; case GRAPHICS_DRAW_TEXT_AREA: scaled.text_area.area.x *= scale.x; scaled.text_area.area.y *= scale.y; scaled.text_area.area.width *= scale.x; scaled.text_area.area.height *= scale.y; scaled.text_area.max_size.x *= scale.x; scaled.text_area.max_size.y *= scale.y; break; case GRAPHICS_DRAW_TEXT_CURSOR: scaled.text_cursor.area.x *= scale.x; scaled.text_cursor.area.y *= scale.y; scaled.text_cursor.area.width *= scale.x; scaled.text_cursor.area.height *= scale.y; scaled.text_cursor.max_size.x *= scale.x; scaled.text_cursor.max_size.y *= scale.y; break; default:; } return scaled; } static Graphics_Request graphics_request_moved_(Graphics_Request req, i64 dx, i64 dy) { Graphics_Request moved = req; switch (req.op) { case GRAPHICS_FILL_TRIANGLE: moved.triangle.vertices[0].x += dx; moved.triangle.vertices[0].y += dy; moved.triangle.vertices[1].x += dx; moved.triangle.vertices[1].y += dy; moved.triangle.vertices[2].x += dx; moved.triangle.vertices[2].y += dy; break; case GRAPHICS_FILL_TRIANGLES: moved.triangles.position.x += dx; moved.triangles.position.y += dy; break; case GRAPHICS_FILL_QUAD: moved.quad.vertices[0].x += dx; moved.quad.vertices[0].y += dy; moved.quad.vertices[1].x += dx; moved.quad.vertices[1].y += dy; moved.quad.vertices[2].x += dx; moved.quad.vertices[2].y += dy; moved.quad.vertices[3].x += dx; moved.quad.vertices[3].y += dy; break; case GRAPHICS_FILL_ELLIPSE: moved.ellipse.area.x += dx; moved.ellipse.area.y += dy; break; case GRAPHICS_FILL_LINE: moved.line.vertices[0].x += dx; moved.line.vertices[0].y += dy; moved.line.vertices[1].x += dx; moved.line.vertices[1].y += dy; break; case GRAPHICS_DRAW_TEXT_AREA: moved.text_area.area.x += dx; moved.text_area.area.y += dy; break; case GRAPHICS_DRAW_TEXT_CURSOR: moved.text_cursor.area.x += dx; moved.text_cursor.area.y += dy; break; default:; } return moved; } static void triangles_area_(f64 *x0, f64 *y0, f64 *x1, f64 *y1, vec2 p, vec2 s, i64 num, vec2 *v) { if (num <= 0) return; *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 = 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); } } static Box graphics_request_area_(Graphics_Request req) { Box area = {0}; switch (req.op) { case GRAPHICS_FILL_ELLIPSE: area = req.ellipse .area; break; case GRAPHICS_DRAW_TEXT_AREA: area = req.text_area .area; break; case GRAPHICS_DRAW_TEXT_CURSOR: area = req.text_cursor.area; break; case GRAPHICS_FILL_TRIANGLE: { vec2 *v = req.triangle.vertices; area.x = min3_(v[0].x, v[1].x, v[2].x); area.y = min3_(v[0].y, v[1].y, v[2].y); area.width = max3_(v[0].x, v[1].x, v[2].x) - area.x; area.height = max3_(v[0].y, v[1].y, v[2].y) - area.y; } break; case GRAPHICS_FILL_TRIANGLES: if (req.triangles.num_triangles > 0) { f64 x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0; triangles_area_(&x0, &y0, &x1, &y1, req.triangles.position, req.triangles.scale, req.triangles.num_triangles, req.triangles.vertices); area.x = x0; area.y = y0; area.width = x1 - x0; area.height = y1 - y0; } break; case GRAPHICS_FILL_QUAD: { vec2 *v = req.quad.vertices; area.x = min4_(v[0].x, v[1].x, v[2].x, v[3].x); area.y = min4_(v[0].y, v[1].y, v[2].y, v[3].y); area.width = max4_(v[0].x, v[1].x, v[2].x, v[3].x) - area.x; area.height = max4_(v[0].y, v[1].y, v[2].y, v[3].y) - area.y; } break; case GRAPHICS_FILL_LINE: { vec2 *v = req.line.vertices; f64 half_w = req.line.width * 0.5; area.x = min2_(v[0].x, v[1].x) - half_w; area.y = min2_(v[0].y, v[1].y) - half_w; area.width = max2_(v[0].x, v[1].x) + half_w - area.x; area.height = max2_(v[0].y, v[1].y) + half_w - area.y; } break; default:; } return area; } static Graphics_Request_Norm_ normalize_graphics_request_(Graphics_Request req) { Box area = graphics_request_area_(req); i64 x = (i64) floor(area.x + 0.5); i64 y = (i64) floor(area.y + 0.5); i64 w = (i64) floor(area.x + area.width + 0.5) - x; i64 h = (i64) floor(area.y + area.height + 0.5) - y; return (Graphics_Request_Norm_) { .req = graphics_request_moved_(req, -x, -y), .x = x, .y = y, .width = w, .height = h, }; } static void perform_graphics_request_to_stencil_(Stencil_Buffer dst, Graphics_Request req) { switch (req.op) { case GRAPHICS_FILL_TRIANGLE: fill_triangle_to_stencil( dst, 255, req.triangle.vertices ); break; case GRAPHICS_FILL_TRIANGLES: fill_triangles_to_stencil( dst, 255, req.triangles.position, req.triangles.scale, req.triangles.num_triangles, req.triangles.vertices ); break; case GRAPHICS_FILL_QUAD: fill_quad_to_stencil( dst, 255, req.quad.vertices ); break; case GRAPHICS_FILL_ELLIPSE: fill_ellipse_to_stencil( dst, 255, req.ellipse.area ); break; case GRAPHICS_FILL_LINE: fill_line_to_stencil( dst, 255, req.line.vertices, req.line.width ); break; case GRAPHICS_DRAW_TEXT_AREA: draw_text_area_to_stencil( dst, 255, req.text_area.font, req.text_area.area, req.text_area.max_size, req.text_area.num_chars, req.text_area.text ); break; case GRAPHICS_DRAW_TEXT_CURSOR: draw_text_cursor_to_stencil( dst, 255, req.text_cursor.font, req.text_cursor.area, req.text_cursor.max_size, req.text_cursor.num_chars, req.text_cursor.text, req.text_cursor.cursor, req.text_cursor.selection ); break; default:; } } static void perform_graphics_request_cached_(Pixel_Buffer dst, vec4_f32 color, Graphics_Request_Norm_ norm) { 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 norm.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_stencil_((Stencil_Buffer) { .width = s->width, .height = s->height, .stride = s->width, .pixels = s->buffer, }, s->req); } } Box area = { .x = norm.x, .y = norm.y, .width = norm.width, .height = norm.height, }; Stencil_Buffer stencil = {0}; if (s->occupied) stencil = (Stencil_Buffer) { .width = s->width, .height = s->height, .stride = s->width, .pixels = s->buffer, }; else { stencil = default_stencil(norm.width, norm.height); perform_graphics_request_to_stencil_(stencil, norm.req); } fill_stencil_to_buffer(dst, color, area, stencil); } static Pixel_Buffer default_pixel_buffer_(void) { i64 max_height = g_platform.num_pixels / g_platform.frame_width; return (Pixel_Buffer) { .width = g_platform.frame_width, .height = min2_i64_(max_height, g_platform.frame_height), .stride = g_platform.frame_width, .pixels = g_platform.pixels, }; } static vec2 default_scale_(void) { return (vec2) { .x = ((f64) g_platform.frame_width) / g_platform.real_width, .y = ((f64) g_platform.frame_height) / g_platform.real_height, }; } static Graphics_Context graphics_context_defaults_(Graphics_Context context) { if (context.dst.pixels == NULL) { context.dst = default_pixel_buffer_(); if (context.scale.x == 0.0 && context.scale.y == 0.0) context.scale = default_scale_(); } return context; } void perform_graphics_request(Graphics_Context context, Graphics_Request req) { context = graphics_context_defaults_(context); Graphics_Request scaled = graphics_request_scaled_(req, context.scale); Graphics_Request_Norm_ norm = normalize_graphics_request_(scaled); if (norm.width <= 0 || norm.height <= 0) return; if (context.disable_cache) { Stencil_Buffer stencil = default_stencil(norm.width, norm.height); Box area = { .x = norm.x, .y = norm.y, .width = norm.width, .height = norm.height, }; perform_graphics_request_to_stencil_(stencil, norm.req); fill_stencil_to_buffer(context.dst, context.color, area, stencil); } else perform_graphics_request_cached_(context.dst, context.color, norm); } void fill_triangle_cached(vec4_f32 color, vec2 vertices[3]) { perform_graphics_request( (Graphics_Context) {0}, (Graphics_Request) { .op = GRAPHICS_FILL_TRIANGLE, .triangle = { .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 = { .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 = { .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 = { .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 = { .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, .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, .area = area, .max_size = max_size, .num_chars = num_chars, .text = text, .cursor = cursor, .selection = selection, }, } ); } Box area_scaled_(Box area, vec2 scale) { return (Box) { .x = area.x * scale.x, .y = area.y * scale.y, .width = area.width * scale.x, .height = area.height * scale.y, }; } void draw_pixels(Box area, Pixel_Buffer src) { draw_pixels_to_buffer(default_pixel_buffer_(), area_scaled_(area, default_scale_()), src); } void fill_rectangle(vec4_f32 color, Box area) { fill_rectangle_to_buffer(default_pixel_buffer_(), color, area_scaled_(area, default_scale_())); } void fill_triangle(vec4_f32 color, vec2 vertices[3]) { perform_graphics_request( (Graphics_Context) { .disable_cache = 1, }, (Graphics_Request) { .op = GRAPHICS_FILL_TRIANGLE, .triangle = { .vertices = { vertices[0], vertices[1], vertices[2], }, }, } ); } 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 = { .position = position, .scale = scale, .num_triangles = num_triangles, .vertices = vertices, }, } ); } void fill_quad(vec4_f32 color, vec2 vertices[4]) { perform_graphics_request( (Graphics_Context) { .disable_cache = 1, }, (Graphics_Request) { .op = GRAPHICS_FILL_QUAD, .quad = { .vertices = { vertices[0], vertices[1], vertices[2], vertices[3], }, }, } ); } void fill_ellipse(vec4_f32 color, Box area) { perform_graphics_request( (Graphics_Context) { .disable_cache = 1, }, (Graphics_Request) { .op = GRAPHICS_FILL_ELLIPSE, .ellipse = { .area = area, }, } ); } 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 = { .vertices = { vertices[0], vertices[1], }, .width = width, }, } ); } 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, .area = area, .max_size = max_size, .num_chars = num_chars, .text = 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, .area = area, .max_size = max_size, .num_chars = num_chars, .text = text, .cursor = cursor, .selection = selection, }, } ); } void clean_graphics_requests_cache(i64 amount) { for (; amount > 0; --amount) { Graphics_Cache_Slot_ *s = _graphics_cache + _graphics_cache_gc_index; i64 required_len = !s->occupied ? 0 : s->width * s->height; if (s->buffer_len > required_len) resize_dynamic_array_exact(&s->buffer_len, (void **) &s->buffer, sizeof *s->buffer, required_len); _graphics_cache_gc_index = (_graphics_cache_gc_index + 1) % GRAPHICS_CACHE_SIZE; } } // ================================================================ // // Test suite // // ================================================================ #if ENABLE_TESTING #define TEST_FILE graphics #include "test.c" TEST("colors") { REQUIRE_EQ(rgb_u32_from_f32((vec3_f32) { 1.f, 0.f, 0.f, }), 0xff0000); REQUIRE_EQ(rgb_u32_from_f32((vec3_f32) { 0.f, 1.f, 0.f, }), 0x00ff00); REQUIRE_EQ(rgb_u32_from_f32((vec3_f32) { 0.f, 0.f, 1.f, }), 0x0000ff); REQUIRE_EQ(rgb_u32_from_f32((vec3_f32) { .5f, 0.f, 0.f, }), 0x800000); REQUIRE_EQ(rgb_u32_from_f32((vec3_f32) { 0.f, .5f, 0.f, }), 0x008000); REQUIRE_EQ(rgb_u32_from_f32((vec3_f32) { 0.f, 0.f, .5f, }), 0x000080); REQUIRE_EQ(rgba_u32_from_f32((vec4_f32) { 1.f, 0.f, 0.f, 0.f, }), 0x00ff0000); REQUIRE_EQ(rgba_u32_from_f32((vec4_f32) { 0.f, 1.f, 0.f, 0.f, }), 0x0000ff00); REQUIRE_EQ(rgba_u32_from_f32((vec4_f32) { 0.f, 0.f, 1.f, 0.f, }), 0x000000ff); REQUIRE_EQ(rgba_u32_from_f32((vec4_f32) { 0.f, 0.f, 0.f, 1.f, }), 0xff000000); REQUIRE_EQ(rgba_u32_from_f32((vec4_f32) { .5f, 0.f, 0.f, 0.f, }), 0x00800000); REQUIRE_EQ(rgba_u32_from_f32((vec4_f32) { 0.f, .5f, 0.f, 0.f, }), 0x00008000); REQUIRE_EQ(rgba_u32_from_f32((vec4_f32) { 0.f, 0.f, .5f, 0.f, }), 0x00000080); REQUIRE_EQ(rgba_u32_from_f32((vec4_f32) { 0.f, 0.f, 0.f, .5f, }), 0x80000000); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 0.f, 0.f, 0.f, }).x * 100, 0); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 1.f, 1.f, 1.f, }).x * 100, 100); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 1.f, 0.f, 0.f, }).x * 100, 62); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 0.f, 1.f, 0.f, }).x * 100, 86); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 0.f, 0.f, 1.f, }).x * 100, 45); REQUIRE_EQ(lch_from_rgb((vec3_f32) { .5f, .5f, .5f, }).y * 100, 0); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 1.f, 0.f, 0.f, }).y * 100, 25); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 0.f, 1.f, 0.f, }).y * 100, 29); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 0.f, 0.f, 1.f, }).y * 100, 31); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 1.f, 0.f, 0.f, }).z * 100, 51); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 0.f, 1.f, 0.f, }).z * 100, 248); REQUIRE_EQ(lch_from_rgb((vec3_f32) { 0.f, 0.f, 1.f, }).z * 100, -167); vec3_f32 white = rgb_from_lch(lch_from_rgb((vec3_f32) { 1.f, 1.f, 1.f, })); REQUIRE_EQ(white.x * 100, 100); REQUIRE_EQ(white.y * 100, 100); REQUIRE_EQ((white.z + 6e-7) * 100, 100); vec3_f32 black = rgb_from_lch(lch_from_rgb((vec3_f32) { 0.f, 0.f, 0.f, })); REQUIRE_EQ(black.x * 100, 0); REQUIRE_EQ(black.y * 100, 0); REQUIRE_EQ(black.z * 100, 0); vec3_f32 grey = rgb_from_lch(lch_from_rgb((vec3_f32) { .5f, .5f, .5f, })); REQUIRE_EQ(grey.x * 100, 50); REQUIRE_EQ((grey.y + 3e-8) * 100, 50); REQUIRE_EQ((grey.z + 3e-7) * 100, 50); vec3_f32 red = rgb_from_lch(lch_from_rgb((vec3_f32) { 1.f, 0.f, 0.f, })); REQUIRE_EQ(red.x * 100, 100); REQUIRE_EQ(red.y * 100, 0); REQUIRE_EQ(red.z * 100, 0); vec3_f32 green = rgb_from_lch(lch_from_rgb((vec3_f32) { 0.f, 1.f, 0.f, })); REQUIRE_EQ(green.x * 100, 0); REQUIRE_EQ((green.y + 3e-7) * 100, 100); REQUIRE_EQ(green.z * 100, 0); vec3_f32 blue = rgb_from_lch(lch_from_rgb((vec3_f32) { 0.f, 0.f, 1.f, })); REQUIRE_EQ(blue.x * 100, 0); REQUIRE_EQ(blue.y * 100, 0); REQUIRE_EQ((blue.z + 3e-7) * 100, 100); vec3_f32 yellow = rgb_from_lch(lch_from_rgb((vec3_f32) { 1.f, 1.f, 0.f, })); REQUIRE_EQ(yellow.x * 100, 100); REQUIRE_EQ((yellow.y + 6e-8) * 100, 100); REQUIRE_EQ(yellow.z * 100, 0); vec3_f32 cyan = rgb_from_lch(lch_from_rgb((vec3_f32) { 0.f, 1.f, 1.f, })); REQUIRE_EQ(cyan.x * 100, 0); REQUIRE_EQ((cyan.y + 3e-7) * 100, 100); REQUIRE_EQ((cyan.z + 3e-7) * 100, 100); vec3_f32 purple = rgb_from_lch(lch_from_rgb((vec3_f32) { 1.f, 0.f, 1.f, })); REQUIRE_EQ(purple.x * 100, 100); REQUIRE_EQ(purple.y * 100, 0); REQUIRE_EQ((purple.z + 3e-7) * 100, 100); } TEST("rectangle corners") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = -1, .y = -1, .width = 2, .height = 2, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 9, .y = -1, .width = 2, .height = 2, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = -1, .y = 9, .width = 2, .height = 2, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 9, .y = 9, .width = 2, .height = 2, } ); b8 expect_bits[100] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("rectangle middle") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 1, .y = 1, .width = 8, .height = 8, } ); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("rectangle aliasing") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 1.4, .y = 1.4, .width = 2.0, .height = 2.0, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 4.0, .y = 1.0, .width = 2.4, .height = 2.4, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 6.6, .y = 1.0, .width = 2.0, .height = 2.0, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 1.0, .y = 3.6, .width = 2.0, .height = 2.0, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 4.0, .y = 4.0, .width = 2.4, .height = 2.4, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 7.4, .y = 4.4, .width = 1.2, .height = 1.2, } ); fill_rectangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, (Box) { .x = 7.44, .y = 6.51, .width = 1.07, .height = 1.991, } ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("triangle") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; fill_triangle_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f }, (vec2[3]) { { 1.0, 1.0 }, { 5.9, 1.0 }, { 1.0, 5.9 }, }, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("triangles") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; fill_triangles_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f }, (vec2) { 1.0, 1.0 }, (vec2) { 1.0, 1.0 }, 2, (vec2[6]) { { 0.0, 0.0 }, { 3.1, 0.0 }, { 0.0, 3.1 }, { 4.1, 0.9 }, { 0.9, 4.1 }, { 4.1, 4.1 }, }, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("quad") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; fill_quad_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f }, (vec2[4]) { { 4.0, 0.9 }, { 7.1, 4.0 }, { 4.0, 7.1 }, { 0.9, 4.0 }, }, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("ellipse") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; fill_ellipse_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f }, (Box) { 0.9, 0.9, 9.2, 9.2 }, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("line") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; fill_line_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f }, (vec2[2]) { { 1.0, 1.0 }, { 8.0, 3.0 }, }, 1.0, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("text letter A") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; draw_text_area_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, 0, (Box) { .x = 0, .y = 0, .width = 10, .height = 10, }, (vec2) { .x = 10.0, .y = 10.0, }, 1, (c32[1]) { 'A', }, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("text letter z") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; draw_text_area_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, 0, (Box) { .x = 0, .y = 0, .width = 10, .height = 10, }, (vec2) { .x = 10.0, .y = 10.0, }, 1, (c32[1]) { 'z', }, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("text digits 42") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; draw_text_area_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, 0, (Box) { .x = 0, .y = 0, .width = 10, .height = 10, }, (vec2) { .x = 10.0, .y = 10.0, }, 2, (c32[2]) { '4', '2', }, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("text cursor") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; draw_text_cursor_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, 0, (Box) { .x = 1.0, .y = 1.0, .width = 8.1, .height = 6.1, }, (vec2) { .x = 10.0, .y = 10.0, }, 2, (c32[2]) { 'a', 'a', }, 1, 0, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("text selection forward") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; draw_text_cursor_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, 0, (Box) { .x = 1.0, .y = 1.0, .width = 8.1, .height = 6.1, }, (vec2) { .x = 3.0, .y = 2.0, }, 14, (c32[14]) { 'a', 'a', 'a', 'a', '\n', 'b', 'b', 'b', 'b', '\n', 'c', 'c', 'c', 'c', }, 2, 10, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("text selection backward") { i64 w = 10; i64 h = 10; vec4_f32 pixels[100] = {0}; draw_text_cursor_to_buffer( (Pixel_Buffer) { .width = w, .height = h, .stride = w, .pixels = pixels, }, (vec4_f32) { 1.0f, 1.0f, 1.0f, 1.0f, }, 0, (Box) { .x = 1.0, .y = 1.0, .width = 8.1, .height = 6.1, }, (vec2) { .x = 3.0, .y = 2.0, }, 14, (c32[14]) { 'a', 'a', 'a', 'a', '\n', 'b', 'b', 'b', 'b', '\n', 'c', 'c', 'c', 'c', }, 12, -10, default_stencil(w, h) ); // printf("\n"); // for (i64 j = 0; j < h; ++j) { // for (i64 i = 0; i < w; ++i) { // printf(" %c", pixels[j * w + i].x > 0.5 ? 'x' : '.'); // } // printf("\n"); // } // printf("\n"); b8 expect_bits[100] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b8 ok = 1; for (i64 i = 0; i < w * h; ++i) { if (expect_bits[i] != (pixels[i].x > 0.5) || expect_bits[i] != (pixels[i].y > 0.5) || expect_bits[i] != (pixels[i].z > 0.5) || expect_bits[i] != (pixels[i].w > 0.5)) { ok = 0; break; } } REQUIRE(ok); } TEST("scale down exact") { vec4_f32 src[16] = { { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; vec4_f32 dst[4]; vec4_f32 expect[4] = { { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; draw_pixels_to_buffer( (Pixel_Buffer) { .width = 2, .height = 2, .stride = 2, .pixels = dst, }, (Box) { .x = 0.0, .y = 0.0, .width = 2.0, .height = 2.0, }, (Pixel_Buffer) { .width = 4, .height = 4, .stride = 4, .pixels = src, } ); // for (i64 i = 0; i < (i64) (sizeof dst / sizeof *dst); ++i) { // printf("%g, %g, %g, %g \n", dst[i].x, dst[i].y, dst[i].z, dst[i].w); // } b8 ok = 1; for (i64 i = 0; i < 4; ++i) ok = ok && dst[i].x == expect[i].x && dst[i].y == expect[i].y && dst[i].z == expect[i].z && dst[i].w == expect[i].w; REQUIRE(ok); } TEST("scale down aliasing overflow") { vec4_f32 src[16] = { { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; vec4_f32 dst[4]; vec4_f32 expect[4] = { { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; draw_pixels_to_buffer( (Pixel_Buffer) { .width = 2, .height = 2, .stride = 2, .pixels = dst, }, (Box) { .x = 0.4, .y = 0.4, .width = 2.0, .height = 2.0, }, (Pixel_Buffer) { .width = 4, .height = 4, .stride = 4, .pixels = src, } ); // for (i64 i = 0; i < (i64) (sizeof dst / sizeof *dst); ++i) { // printf("%g, %g, %g, %g \n", dst[i].x, dst[i].y, dst[i].z, dst[i].w); // } b8 ok = 1; for (i64 i = 0; i < 4; ++i) ok = ok && dst[i].x == expect[i].x && dst[i].y == expect[i].y && dst[i].z == expect[i].z && dst[i].w == expect[i].w; REQUIRE(ok); } TEST("scale down aliasing underflow") { vec4_f32 src[16] = { { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; vec4_f32 dst[4]; vec4_f32 expect[4] = { { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; draw_pixels_to_buffer( (Pixel_Buffer) { .width = 2, .height = 2, .stride = 2, .pixels = dst, }, (Box) { .x = -0.4, .y = -0.4, .width = 2.0, .height = 2.0, }, (Pixel_Buffer) { .width = 4, .height = 4, .stride = 4, .pixels = src, } ); // for (i64 i = 0; i < (i64) (sizeof dst / sizeof *dst); ++i) { // printf("%g, %g, %g, %g \n", dst[i].x, dst[i].y, dst[i].z, dst[i].w); // } b8 ok = 1; for (i64 i = 0; i < 4; ++i) ok = ok && dst[i].x == expect[i].x && dst[i].y == expect[i].y && dst[i].z == expect[i].z && dst[i].w == expect[i].w; REQUIRE(ok); } TEST("scale down aliasing middle") { vec4_f32 src[9] = { { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 1.0, 1.0 }, { 1.0, 0.0, 1.0, 1.0 }, { 1.0, 0.0, 0.5, 1.0 }, { 0.5, 1.0, 0.0, 1.0 }, { 0.0, 0.5, 1.0, 1.0 }, }; vec4_f32 dst[1]; vec4_f32 expect[1] = { { 0.0, 1.0, 1.0, 1.0 }, }; draw_pixels_to_buffer( (Pixel_Buffer) { .width = 1, .height = 1, .stride = 1, .pixels = dst, }, (Box) { .x = 0.0, .y = 0.0, .width = 1.0, .height = 1.0, }, (Pixel_Buffer) { .width = 3, .height = 3, .stride = 3, .pixels = src, } ); // for (i64 i = 0; i < (i64) (sizeof dst / sizeof *dst); ++i) { // printf("%g, %g, %g, %g \n", dst[i].x, dst[i].y, dst[i].z, dst[i].w); // } b8 ok = 1; for (i64 i = 0; i < 1; ++i) ok = ok && dst[i].x == expect[i].x && dst[i].y == expect[i].y && dst[i].z == expect[i].z && dst[i].w == expect[i].w; REQUIRE(ok); } TEST("scale up exact") { vec4_f32 src[4] = { { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; vec4_f32 dst[16]; vec4_f32 expect[16] = { { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; draw_pixels_to_buffer( (Pixel_Buffer) { .width = 4, .height = 4, .stride = 4, .pixels = dst, }, (Box) { .x = 0.0, .y = 0.0, .width = 4.0, .height = 4.0, }, (Pixel_Buffer) { .width = 2, .height = 2, .stride = 2, .pixels = src, } ); // for (i64 i = 0; i < (i64) (sizeof dst / sizeof *dst); ++i) { // printf("%g, %g, %g, %g \n", dst[i].x, dst[i].y, dst[i].z, dst[i].w); // } b8 ok = 1; for (i64 i = 0; i < 16; ++i) ok = ok && dst[i].x == expect[i].x && dst[i].y == expect[i].y && dst[i].z == expect[i].z && dst[i].w == expect[i].w; REQUIRE(ok); } TEST("scale up aliasing overflow") { vec4_f32 src[4] = { { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; vec4_f32 dst[16]; vec4_f32 expect[16] = { { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; draw_pixels_to_buffer( (Pixel_Buffer) { .width = 4, .height = 4, .stride = 4, .pixels = dst, }, (Box) { .x = 0.4, .y = 0.4, .width = 4.0, .height = 4.0, }, (Pixel_Buffer) { .width = 2, .height = 2, .stride = 2, .pixels = src, } ); // for (i64 i = 0; i < (i64) (sizeof dst / sizeof *dst); ++i) { // printf("%g, %g, %g, %g \n", dst[i].x, dst[i].y, dst[i].z, dst[i].w); // } b8 ok = 1; for (i64 i = 0; i < 16; ++i) ok = ok && dst[i].x == expect[i].x && dst[i].y == expect[i].y && dst[i].z == expect[i].z && dst[i].w == expect[i].w; REQUIRE(ok); } TEST("scale up aliasing underflow") { vec4_f32 src[4] = { { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; vec4_f32 dst[16]; vec4_f32 expect[16] = { { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 0.0, 0.0, 1.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0, 1.0 }, }; draw_pixels_to_buffer( (Pixel_Buffer) { .width = 4, .height = 4, .stride = 4, .pixels = dst, }, (Box) { .x = -0.4, .y = -0.4, .width = 4.0, .height = 4.0, }, (Pixel_Buffer) { .width = 2, .height = 2, .stride = 2, .pixels = src, } ); // for (i64 i = 0; i < (i64) (sizeof dst / sizeof *dst); ++i) { // printf("%g, %g, %g, %g \n", dst[i].x, dst[i].y, dst[i].z, dst[i].w); // } b8 ok = 1; for (i64 i = 0; i < 16; ++i) ok = ok && dst[i].x == expect[i].x && dst[i].y == expect[i].y && dst[i].z == expect[i].z && dst[i].w == expect[i].w; REQUIRE(ok); } TEST("cached requests") { // TODO } #undef TEST_FILE #endif // ENABLE_TESTING // ================================================================ #endif // GRAPHICS_IMPL_GUARD_ #endif // GRAPHICS_HEADER