293 lines
8.1 KiB
C
293 lines
8.1 KiB
C
#include <math.h>
|
|
#include <png.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/random.h>
|
|
#include <time.h>
|
|
|
|
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);
|
|
}
|