From 72b9fc8c1515c12600513fd9759f9b9034825726 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Sat, 26 Apr 2025 02:10:48 +0200 Subject: Pixels scaling tests and perf --- graphics.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 409 insertions(+), 42 deletions(-) (limited to 'graphics.c') diff --git a/graphics.c b/graphics.c index 5632712..a63f622 100644 --- a/graphics.c +++ b/graphics.c @@ -164,9 +164,9 @@ typedef struct { // // ================================================================ -Box font_text_area_dispatch(i32 font, vec2 scale, i64 num_chars, c32 *text); +Box dispatch_font_text_area(i32 font, vec2 scale, i64 num_chars, c32 *text); -void font_render_to_stencil_dispatch(Stencil_Buffer dst, i32 font, vec2 position, 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); // ================================================================ @@ -431,8 +431,8 @@ vec3_f32 lch_mix(vec3_f32 a, vec3_f32 b, f32 t) { 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; + 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, @@ -442,9 +442,9 @@ vec3_f32 lch_lerp(vec3_f32 a, vec3_f32 b, f32 t) { } 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); + 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; @@ -455,17 +455,17 @@ u32 rgb_u32_from_f32(vec3_f32 color) { 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, + .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. + .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); + 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; @@ -477,10 +477,10 @@ u32 rgba_u32_from_f32(vec4_f32 color) { 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, + .x = ((color & 0x00ff0000) >> 16) / 255.0f, + .y = ((color & 0x0000ff00) >> 8) / 255.0f, + .z = (color & 0x000000ff) / 255.0f, + .w = ((color & 0xff000000) >> 24) / 255.0f, }; } @@ -621,30 +621,31 @@ void draw_pixels_to_buffer(Pixel_Buffer dst, Box area, Pixel_Buffer src) { 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); + 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); - 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 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); - // FIXME, PERF: Use integer arithmetic. + u32 x_half = x_ratio / 2; + u32 y_half = y_ratio / 2; - 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; + 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 + (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]); + 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); @@ -1223,8 +1224,8 @@ static void lcd_draw_text_(Pixel_Buffer dst, vec4_f32 color, f64 x0, f64 y0, f64 } } -#if !defined(GRAPHICS_ENABLE_FONT_CUSTOM_DISPATCH) -Box font_text_area_dispatch(i32 font, vec2 scale, i64 num_chars, c32 *text) { +#if !defined(ENABLE_FONT_CUSTOM_DISPATCH) +Box dispatch_font_text_area(i32 font, vec2 scale, i64 num_chars, c32 *text) { // TODO (void) font; @@ -1235,7 +1236,7 @@ Box font_text_area_dispatch(i32 font, vec2 scale, i64 num_chars, c32 *text) { return (Box) {0}; } -void font_render_to_stencil_dispatch(Stencil_Buffer dst, i32 font, vec2 position, 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) { // TODO (void) dst; @@ -1245,7 +1246,7 @@ void font_render_to_stencil_dispatch(Stencil_Buffer dst, i32 font, vec2 position (void) num_chars; (void) text; } -#endif // !defined(GRAPHICS_ENABLE_FONT_CUSTOM_DISPATCH) +#endif // !defined(ENABLE_FONT_CUSTOM_DISPATCH) 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; @@ -3058,6 +3059,372 @@ TEST("text selection backward") { 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); +} + #undef TEST_FILE #endif // ENABLE_TESTING -- cgit v1.2.3