Skip to content

Commit 2525ef5

Browse files
jservBennctu
andcommitted
Add preliminary performance tester
mado-perf is a standalone benchmarking program, modeled after x11perf, that evaluates the performance of compositing functions. Expected output format: 2000 reps @ 0.0140 msec ( 71523084.1/sec): 1x1 argb32 source 2000 reps @ 0.0140 msec ( 71523084.1/sec): 1x1 argb32 source 2000 reps @ 0.0130 msec ( 77032700.4/sec): 1x1 argb32 source ... 2000 reps @ 0.0140 msec ( 71523084.1/sec): 1x1 argb32 source 80000 trep @ 0.5578 msec ( 73726930.6/sec): 1x1 argb32 source Co-authored-by: Ben You <[email protected]>
1 parent c9df0a6 commit 2525ef5

File tree

4 files changed

+359
-0
lines changed

4 files changed

+359
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*.a
55
demo-*
66
.demo-*
7+
mado-perf
8+
.mado-perf
79
font-edit
810
.font-edit
911

Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ libtwin.a_cflags-y += $(shell pkg-config --cflags neatvnc aml pixman-1)
131131
TARGET_LIBS += $(shell pkg-config --libs neatvnc aml pixman-1)
132132
endif
133133

134+
# Performance tester
135+
136+
ifeq ($(CONFIG_PERF_TEST), y)
137+
target-$(CONFIG_PERF_TEST) += mado-perf
138+
mado-perf_depends-y += $(target.a-y)
139+
mado-perf_files-y += tools/perf.c
140+
mado-perf_includes-y := include
141+
mado-perf_ldflags-y := \
142+
$(target.a-y) \
143+
$(TARGET_LIBS)
144+
endif
145+
134146
# Standalone application
135147

136148
ifeq ($(CONFIG_DEMO_APPLICATIONS), y)

configs/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,9 @@ config TOOL_FONTEDIT
159159
default y
160160
depends on TOOLS
161161

162+
config PERF_TEST
163+
bool "Build performance tester"
164+
default y
165+
depends on TOOLS
166+
162167
endmenu

tools/perf.c

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
/*
2+
* Twin - A Tiny Window System
3+
* Copyright (c) 2025 National Cheng Kung University, Taiwan
4+
* All rights reserved.
5+
*/
6+
7+
#include <assert.h>
8+
#include <inttypes.h>
9+
#include <stdbool.h>
10+
#include <stdint.h>
11+
#include <stdio.h>
12+
#include <string.h>
13+
#include <sys/time.h>
14+
#include <unistd.h>
15+
16+
#include "twin.h"
17+
18+
#define TEST_PIX_WIDTH 1200
19+
#define TEST_PIX_HEIGHT 800
20+
#define MIN_TEST_REPS 40
21+
#define TARGET_TEST_TIME 500000 /* 0.5 seconds in microseconds */
22+
23+
/* Maximum repetitions to prevent excessive runtime */
24+
#define MAX_REPS 2000000
25+
/* Size-based calibration limits */
26+
#define MAX_REPS_LARGE 20000 /* For operations >= 100x100 */
27+
#define MAX_REPS_MEDIUM 200000 /* For operations >= 10x10 */
28+
29+
static twin_pixmap_t *src32, *dst32, *mask8;
30+
static int test_width, test_height;
31+
32+
/* Sync time adjustment calculation */
33+
static double sync_time_adjustment = 0.0;
34+
35+
static void test_argb32_source_argb32(void)
36+
{
37+
twin_operand_t srco = {.source_kind = TWIN_PIXMAP, .u.pixmap = src32};
38+
twin_composite(dst32, 0, 0, &srco, 0, 0, NULL, 0, 0, TWIN_SOURCE,
39+
test_width, test_height);
40+
}
41+
42+
static void test_argb32_over_argb32(void)
43+
{
44+
twin_operand_t srco = {.source_kind = TWIN_PIXMAP, .u.pixmap = src32};
45+
twin_composite(dst32, 0, 0, &srco, 0, 0, NULL, 0, 0, TWIN_OVER, test_width,
46+
test_height);
47+
}
48+
49+
static void test_argb32_over_argb32_mask(void)
50+
{
51+
twin_operand_t srco = {.source_kind = TWIN_PIXMAP, .u.pixmap = src32};
52+
twin_operand_t masko = {.source_kind = TWIN_PIXMAP, .u.pixmap = mask8};
53+
twin_composite(dst32, 0, 0, &srco, 0, 0, &masko, 0, 0, TWIN_OVER,
54+
test_width, test_height);
55+
}
56+
57+
static void test_solid_source_argb32(void)
58+
{
59+
twin_operand_t srco = {.source_kind = TWIN_SOLID, .u.argb = 0x80ff0000};
60+
twin_composite(dst32, 0, 0, &srco, 0, 0, NULL, 0, 0, TWIN_SOURCE,
61+
test_width, test_height);
62+
}
63+
64+
static void test_solid_over_argb32(void)
65+
{
66+
twin_operand_t srco = {.source_kind = TWIN_SOLID, .u.argb = 0x80ff0000};
67+
twin_composite(dst32, 0, 0, &srco, 0, 0, NULL, 0, 0, TWIN_OVER, test_width,
68+
test_height);
69+
}
70+
71+
static void test_solid_over_argb32_mask(void)
72+
{
73+
twin_operand_t srco = {.source_kind = TWIN_SOLID, .u.argb = 0x80ff0000};
74+
twin_operand_t masko = {.source_kind = TWIN_PIXMAP, .u.pixmap = mask8};
75+
twin_composite(dst32, 0, 0, &srco, 0, 0, &masko, 0, 0, TWIN_OVER,
76+
test_width, test_height);
77+
}
78+
79+
/* Test with different alpha levels */
80+
static void test_solid_over_argb32_25(void)
81+
{
82+
twin_operand_t srco = {.source_kind = TWIN_SOLID, .u.argb = 0x40ff0000};
83+
twin_composite(dst32, 0, 0, &srco, 0, 0, NULL, 0, 0, TWIN_OVER, test_width,
84+
test_height);
85+
}
86+
87+
static void test_solid_over_argb32_50(void)
88+
{
89+
twin_operand_t srco = {.source_kind = TWIN_SOLID, .u.argb = 0x80ff0000};
90+
twin_composite(dst32, 0, 0, &srco, 0, 0, NULL, 0, 0, TWIN_OVER, test_width,
91+
test_height);
92+
}
93+
94+
static void test_solid_over_argb32_75(void)
95+
{
96+
twin_operand_t srco = {.source_kind = TWIN_SOLID, .u.argb = 0xc0ff0000};
97+
twin_composite(dst32, 0, 0, &srco, 0, 0, NULL, 0, 0, TWIN_OVER, test_width,
98+
test_height);
99+
}
100+
101+
static void test_solid_over_argb32_opaque(void)
102+
{
103+
twin_operand_t srco = {.source_kind = TWIN_SOLID, .u.argb = 0xffff0000};
104+
twin_composite(dst32, 0, 0, &srco, 0, 0, NULL, 0, 0, TWIN_OVER, test_width,
105+
test_height);
106+
}
107+
108+
/* Measure sync time by calling gettimeofday() repeatedly */
109+
static void measure_sync_time(void)
110+
{
111+
struct timeval start, end;
112+
int reps = 10000;
113+
114+
gettimeofday(&start, NULL);
115+
for (int i = 0; i < reps; i++)
116+
gettimeofday(&end, NULL);
117+
gettimeofday(&end, NULL);
118+
119+
uint64_t start_us = (uint64_t) start.tv_sec * 1000000U + start.tv_usec;
120+
uint64_t end_us = (uint64_t) end.tv_sec * 1000000U + end.tv_usec;
121+
122+
sync_time_adjustment = ((double) (end_us - start_us)) / reps / 1000.0;
123+
}
124+
125+
/* Calibrate test repetitions to run for approximately TARGET_TEST_TIME */
126+
static int calibrate_reps(void (*test_func)(void))
127+
{
128+
struct timeval start, end;
129+
int reps = 1000; /* Start with a reasonable base */
130+
uint64_t elapsed_us;
131+
int area = test_width * test_height;
132+
int max_reps_for_size;
133+
uint64_t target_time_for_size;
134+
135+
/* Adjust limits based on operation size */
136+
if (area >= 10000) { /* 100x100 or larger */
137+
max_reps_for_size = MAX_REPS_LARGE;
138+
target_time_for_size = TARGET_TEST_TIME / 2; /* 250ms for large ops */
139+
reps = 200; /* Start smaller for large operations */
140+
} else if (area >= 100) { /* 10x10 or larger */
141+
max_reps_for_size = MAX_REPS_MEDIUM;
142+
target_time_for_size =
143+
TARGET_TEST_TIME * 3 / 4; /* 375ms for medium ops */
144+
reps = 1000;
145+
} else { /* Small operations (1x1, etc.) */
146+
max_reps_for_size = MAX_REPS;
147+
target_time_for_size = TARGET_TEST_TIME; /* Full 500ms for tiny ops */
148+
reps = 2000;
149+
}
150+
151+
/* Start with a reasonable number and scale up gradually */
152+
do {
153+
gettimeofday(&start, NULL);
154+
for (int i = 0; i < reps; i++)
155+
test_func();
156+
gettimeofday(&end, NULL);
157+
158+
elapsed_us = ((uint64_t) end.tv_sec * 1000000U + end.tv_usec) -
159+
((uint64_t) start.tv_sec * 1000000U + start.tv_usec);
160+
161+
/* If too fast, increase reps but cap at size-appropriate MAX_REPS */
162+
if (elapsed_us < target_time_for_size / 4 &&
163+
reps < max_reps_for_size / 4) {
164+
reps *= 4;
165+
} else if (elapsed_us < target_time_for_size / 2 &&
166+
reps < max_reps_for_size / 2) {
167+
reps *= 2;
168+
} else {
169+
break;
170+
}
171+
} while (reps < max_reps_for_size);
172+
173+
/* Calculate final repetitions for target time, but respect size limits */
174+
if (elapsed_us > 0 && reps < max_reps_for_size) {
175+
int target_reps =
176+
(int) ((double) reps * target_time_for_size / elapsed_us);
177+
reps =
178+
(target_reps > max_reps_for_size) ? max_reps_for_size : target_reps;
179+
}
180+
181+
return reps > 0 ? (area >= 10000 ? 200 : area >= 100 ? 1000 : 2000) : reps;
182+
}
183+
184+
static void run_test_series(const char *test_name,
185+
void (*test_func)(void),
186+
int width,
187+
int height)
188+
{
189+
test_width = width;
190+
test_height = height;
191+
192+
/* Calibrate repetitions */
193+
int reps = calibrate_reps(test_func);
194+
195+
/* Run multiple test iterations */
196+
double total_rate = 0.0;
197+
int num_runs = MIN_TEST_REPS;
198+
199+
for (int run = 0; run < num_runs; run++) {
200+
struct timeval start, end;
201+
202+
gettimeofday(&start, NULL);
203+
for (int i = 0; i < reps; i++)
204+
test_func();
205+
gettimeofday(&end, NULL);
206+
207+
uint64_t start_us = (uint64_t) start.tv_sec * 1000000U + start.tv_usec;
208+
uint64_t end_us = (uint64_t) end.tv_sec * 1000000U + end.tv_usec;
209+
210+
double elapsed_ms =
211+
((double) (end_us - start_us)) / 1000.0 - sync_time_adjustment;
212+
double rate = (elapsed_ms > 0.0) ? (reps * 1000.0) / elapsed_ms : 0.0;
213+
214+
printf(" %8d reps @ %8.4f msec (%12.1f/sec): %s\n", reps, elapsed_ms,
215+
rate, test_name);
216+
217+
total_rate += rate;
218+
}
219+
220+
/* Print summary */
221+
uint64_t total_reps = (uint64_t) reps * num_runs;
222+
double avg_rate = total_rate / num_runs;
223+
/* Calculate actual average time from the total rate */
224+
double avg_time = (avg_rate > 0.0) ? (total_reps * 1000.0) / avg_rate : 0.0;
225+
226+
printf("%11" PRIu64 " trep @ %8.4f msec (%12.1f/sec): %s\n", total_reps,
227+
avg_time, avg_rate, test_name);
228+
printf("\n");
229+
}
230+
231+
static void run_basic_tests(void)
232+
{
233+
/* Basic pixmap composition tests */
234+
run_test_series("1x1 argb32 source", test_argb32_source_argb32, 1, 1);
235+
run_test_series("1x1 argb32 over", test_argb32_over_argb32, 1, 1);
236+
run_test_series("1x1 argb32 over mask", test_argb32_over_argb32_mask, 1, 1);
237+
238+
run_test_series("10x10 argb32 source", test_argb32_source_argb32, 10, 10);
239+
run_test_series("10x10 argb32 over", test_argb32_over_argb32, 10, 10);
240+
run_test_series("10x10 argb32 over mask", test_argb32_over_argb32_mask, 10,
241+
10);
242+
243+
run_test_series("100x100 argb32 source", test_argb32_source_argb32, 100,
244+
100);
245+
run_test_series("100x100 argb32 over", test_argb32_over_argb32, 100, 100);
246+
run_test_series("100x100 argb32 over mask", test_argb32_over_argb32_mask,
247+
100, 100);
248+
}
249+
250+
static void run_solid_tests(void)
251+
{
252+
/* Solid color composition tests */
253+
run_test_series("1x1 solid source", test_solid_source_argb32, 1, 1);
254+
run_test_series("1x1 solid over", test_solid_over_argb32, 1, 1);
255+
run_test_series("1x1 solid over mask", test_solid_over_argb32_mask, 1, 1);
256+
257+
run_test_series("10x10 solid source", test_solid_source_argb32, 10, 10);
258+
run_test_series("10x10 solid over", test_solid_over_argb32, 10, 10);
259+
run_test_series("10x10 solid over mask", test_solid_over_argb32_mask, 10,
260+
10);
261+
262+
run_test_series("100x100 solid source", test_solid_source_argb32, 100, 100);
263+
run_test_series("100x100 solid over", test_solid_over_argb32, 100, 100);
264+
run_test_series("100x100 solid over mask", test_solid_over_argb32_mask, 100,
265+
100);
266+
}
267+
268+
static void run_alpha_tests(void)
269+
{
270+
/* Alpha transparency tests */
271+
run_test_series("100x100 solid over 25%", test_solid_over_argb32_25, 100,
272+
100);
273+
run_test_series("100x100 solid over 50%", test_solid_over_argb32_50, 100,
274+
100);
275+
run_test_series("100x100 solid over 75%", test_solid_over_argb32_75, 100,
276+
100);
277+
run_test_series("100x100 solid over opaque", test_solid_over_argb32_opaque,
278+
100, 100);
279+
}
280+
281+
static void run_large_tests(void)
282+
{
283+
/* Large area tests */
284+
run_test_series("500x500 argb32 source", test_argb32_source_argb32, 500,
285+
500);
286+
run_test_series("500x500 argb32 over", test_argb32_over_argb32, 500, 500);
287+
run_test_series("500x500 solid over", test_solid_over_argb32, 500, 500);
288+
}
289+
290+
int main(void)
291+
{
292+
time_t now;
293+
char hostname[256];
294+
295+
/* Print header similar to x11perf */
296+
time(&now);
297+
if (gethostname(hostname, sizeof(hostname)) != 0)
298+
strcpy(hostname, "localhost");
299+
300+
printf("Mado performance tester on %s\n", hostname);
301+
printf("%s", ctime(&now));
302+
303+
/* Measure sync time adjustment */
304+
measure_sync_time();
305+
printf("Sync time adjustment is %.4f msecs.\n\n", sync_time_adjustment);
306+
307+
/* Create test pixmaps */
308+
src32 = twin_pixmap_from_file("assets/tux.png", TWIN_ARGB32);
309+
assert(src32);
310+
dst32 = twin_pixmap_create(TWIN_ARGB32, TEST_PIX_WIDTH, TEST_PIX_HEIGHT);
311+
assert(dst32);
312+
mask8 = twin_pixmap_create(TWIN_A8, TEST_PIX_WIDTH, TEST_PIX_HEIGHT);
313+
assert(mask8);
314+
315+
/* Fill destination pixmap */
316+
twin_fill(dst32, 0x80112233, TWIN_SOURCE, 0, 0, TEST_PIX_WIDTH,
317+
TEST_PIX_HEIGHT);
318+
319+
/* Create gradient mask for masking tests */
320+
twin_fill(mask8, 0x80808080, TWIN_SOURCE, 0, 0, TEST_PIX_WIDTH,
321+
TEST_PIX_HEIGHT);
322+
323+
/* Pre-touch data */
324+
test_width = 1;
325+
test_height = 1;
326+
test_argb32_source_argb32();
327+
328+
/* Run comprehensive test series */
329+
run_basic_tests();
330+
run_solid_tests();
331+
run_alpha_tests();
332+
run_large_tests();
333+
334+
/* Cleanup */
335+
twin_pixmap_destroy(src32);
336+
twin_pixmap_destroy(dst32);
337+
twin_pixmap_destroy(mask8);
338+
339+
return 0;
340+
}

0 commit comments

Comments
 (0)