#include #include #include #include #include #include #include #include float noise(float x, float y); float linear_interpolation(float a, float b, float t); float quintic_curve(float t); float dot_product(float a[], float b[]); void pseudo_random_gradient_vector_get(float vec[2], float x, float y); void *calculate(void *args); void single_threaded(unsigned char *buffer, int height, int width); void multi_threaded(unsigned char *buffer, int height, int width, int thread_no); void panic(const char *message); void benchmark_calculations(unsigned char *buffer, int height, int width, int thread_no); void write_image_with_timing(png_image *img, const unsigned char *buffer); void print_timediff(const char *message, struct timespec start, struct timespec end); static unsigned char random_bytes[1024]; enum threaded { THRD_MULTI, THRD_SINGLE }; struct single_threaded_arg { enum threaded type; unsigned char *buffer; int height; int width; }; struct multi_threaded_arg { enum threaded type; unsigned char *buffer; int height_start; int height_end; int width; }; int main(int argc, char *argv[]) { int n = getrandom(random_bytes, 1024, 0); if (n != 1024) { fprintf(stderr, "err: getrandom didn't work out as planned\n"); return 1; } if (argc < 3) { fprintf(stderr, "usage: width height [threads] \n"); return 1; } int width = atoi(argv[1]), height = atoi(argv[2]); int threads = 1; if (argc == 4) { threads = atoi(argv[3]); if (height < threads) { fprintf(stderr, "err: height < threads. expected: height >= threads. aborting\n"); return 1; } printf("rows per thread: %u\n", height / threads); } png_image img; memset(&img, 0, sizeof(img)); img.format = PNG_FORMAT_GRAY; img.width = width; img.height = height; img.version = PNG_IMAGE_VERSION; unsigned char *buffer; buffer = malloc(PNG_IMAGE_SIZE(img)); benchmark_calculations(buffer, height, width, threads); write_image_with_timing(&img, buffer); free(buffer); } void benchmark_calculations(unsigned char *buffer, int height, int width, int threads) { struct timespec start, end; for (int i = 0; i < 3; i++) { printf("performing warmup run #%u, stay tuned!\n", i + 1); clock_gettime(CLOCK_MONOTONIC, &start); if (threads < 2) { single_threaded(buffer, height, width); } else { multi_threaded(buffer, height, width, threads); } clock_gettime(CLOCK_MONOTONIC, &end); print_timediff("warmup calculation time: ", start, end); } puts("performing 10 runs!"); for (int i = 0; i < 10; i++) { clock_gettime(CLOCK_MONOTONIC, &start); if (threads < 2) { single_threaded(buffer, height, width); } else { multi_threaded(buffer, height, width, threads); } clock_gettime(CLOCK_MONOTONIC, &end); print_timediff("calculation time: ", start, end); } } void single_threaded(unsigned char *buffer, int height, int width) { struct single_threaded_arg arg = { .type = THRD_SINGLE, .buffer = buffer, .height = height, .width = width}; calculate(&arg); } void multi_threaded(unsigned char *buffer, int height, int width, int thread_no) { pthread_t threads[thread_no]; const unsigned int rows_per_thread = height / thread_no; struct multi_threaded_arg *all_args[thread_no]; for (int i = 0; i < thread_no; i++) { const int row_start = i * rows_per_thread; int row_end = (i + 1) * rows_per_thread; if (i == thread_no - 1) { row_end = height; } struct multi_threaded_arg *arg; arg = malloc(sizeof *arg); *arg = (struct multi_threaded_arg){.type = THRD_MULTI, .buffer = buffer, .height_start = row_start, .height_end = row_end, .width = width}; pthread_create(&threads[i], NULL, calculate, arg); all_args[i] = arg; } for (int i = 0; i < thread_no; ++i) { pthread_join(threads[i], NULL); free(all_args[i]); } } void *calculate(void *args) { int x = 0; int height = 0, width = 0; unsigned char *buffer; if (args != NULL) { enum threaded *threaded = (enum threaded *)args; if (*threaded == THRD_SINGLE) { struct single_threaded_arg *arg = (struct single_threaded_arg *)args; height = arg->height; width = arg->width; buffer = arg->buffer; } else if (*threaded == THRD_MULTI) { struct multi_threaded_arg *arg = (struct multi_threaded_arg *)args; x = arg->height_start; height = arg->height_end; width = arg->width; buffer = arg->buffer; } } /* minimum space needed for any variation of error msg */ char msg[31 + 13 + 11 + 1]; if (height == 0 || width == 0) { strcat(msg, "things have got seriously wrong"); if (height == 0) { strcat(msg, " height is 0!"); } else { strcat(msg, " width is 0!"); } strcat(msg, " aborting\n"); panic(msg); } for (; x < height; ++x) { for (int y = 0; y < width; ++y) { float noise_val = noise(x * 0.05, y * 0.05); unsigned char pixel = (unsigned char)((noise_val + 1.0f) * 127.5f); buffer[y + x * width] = pixel; } } return NULL; } void panic(const char *message) { fputs(message, stderr); exit(1); } float noise(float x, float y) { int left = floorf(x); int top = floorf(y); float point_in_quad_x = x - left; float point_in_quad_y = y - top; float top_left_gradient[2], top_right_gradient[2], bottom_left_gradient[2], bottom_right_gradient[2]; pseudo_random_gradient_vector_get(top_left_gradient, left, top); pseudo_random_gradient_vector_get(top_right_gradient, left + 1, top); pseudo_random_gradient_vector_get(bottom_left_gradient, left, top + 1); pseudo_random_gradient_vector_get(bottom_right_gradient, left + 1, top + 1); float distance_to_top_left[] = {point_in_quad_x, point_in_quad_y}; float distance_to_top_right[] = {point_in_quad_x - 1, point_in_quad_y}; float distance_to_bottom_left[] = {point_in_quad_x, point_in_quad_y - 1}; float distance_to_bottom_right[] = {point_in_quad_x - 1, point_in_quad_y - 1}; float tx1 = dot_product(distance_to_top_left, top_left_gradient); float tx2 = dot_product(distance_to_top_right, top_right_gradient); float bx1 = dot_product(distance_to_bottom_left, bottom_left_gradient); float bx2 = dot_product(distance_to_bottom_right, bottom_right_gradient); point_in_quad_x = quintic_curve(point_in_quad_x); point_in_quad_y = quintic_curve(point_in_quad_y); float tx = linear_interpolation(tx1, tx2, point_in_quad_x); float bx = linear_interpolation(bx1, bx2, point_in_quad_x); float tb = linear_interpolation(tx, bx, point_in_quad_y); return tb; } float linear_interpolation(float a, float b, float t) { return a + (b - a) * t; } float quintic_curve(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } float dot_product(float a[2], float b[2]) { return a[0] * b[0] + a[1] * b[1]; } void pseudo_random_gradient_vector_get(float vec[2], float x, float y) { int xi = (int)x & 1023; int yi = (int)y & 1023; int hash = random_bytes[(random_bytes[xi] + yi) & 1023]; int v = hash & 3; switch (v) { case 0: vec[0] = 1; vec[1] = 0; break; case 1: vec[0] = -1; vec[1] = 0; break; case 2: vec[0] = 0; vec[1] = 1; break; case 3: vec[0] = 0; vec[1] = -1; break; } } void write_image_with_timing(png_image *img, const unsigned char *buffer) { struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); png_image_write_to_file(img, "noise.png", 0, buffer, 0, NULL); clock_gettime(CLOCK_MONOTONIC, &end); print_timediff("write to file time (single-threaded): ", start, end); } void print_timediff(const char *message, struct timespec start, struct timespec end) { double secs = end.tv_sec - start.tv_sec; double nsecs = (end.tv_nsec - start.tv_nsec) / 1e9; printf("%s%fs.\n", message, secs + nsecs); }