Add comments to main.c
This commit is contained in:
+175
-52
@@ -34,31 +34,79 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
#include "gpu_and_windowing.h"
|
#include "gpu_and_windowing.h"
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// GLOBAL VARIABLES ///
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define MAX_COLUMNS 32
|
#define MAX_COLUMNS 32
|
||||||
|
|
||||||
os_mutex_t frame_mutex;
|
// Parameters. These are set at startup and are
|
||||||
Scene scene;
|
// considered constant after that.
|
||||||
Cubemap skybox;
|
int num_columns;
|
||||||
|
int init_scale;
|
||||||
|
|
||||||
int num_columns = 1;
|
// The scene and background being rendered.
|
||||||
int init_scale = 2;
|
Scene scene;
|
||||||
|
Cubemap skybox;
|
||||||
|
|
||||||
bool workers_should_stop = false;
|
// Any time the accumulation buffer is reset or
|
||||||
|
// resized, this is incremented.
|
||||||
_Atomic uint32_t accum_generation = 0;
|
_Atomic uint32_t accum_generation = 0;
|
||||||
|
|
||||||
|
// This is the "accumulation buffer". Workers evaluate
|
||||||
|
// pixel colors in parallel and sum their results in here.
|
||||||
|
// When the main thread needs to draw a new frame it takes
|
||||||
|
// these values and divides them by the frame count, averaging
|
||||||
|
// the results of multiple frames.
|
||||||
Vector3 *accum = NULL;
|
Vector3 *accum = NULL;
|
||||||
|
|
||||||
|
// This is the "frame buffer". It's only accessed by the
|
||||||
|
// main buffer to store the averaged values of the accumulation
|
||||||
|
// buffer before sending them to the GPU.
|
||||||
Vector3 *frame = NULL;
|
Vector3 *frame = NULL;
|
||||||
int frame_w = 0;
|
|
||||||
int frame_h = 0;
|
// Size of the accumulation and frame buffers
|
||||||
float accum_counts[MAX_COLUMNS] = {0};
|
int frame_w = 0;
|
||||||
|
int frame_h = 0;
|
||||||
|
|
||||||
|
// This guards the critical section around the accumulation buffer.
|
||||||
os_mutex_t frame_mutex;
|
os_mutex_t frame_mutex;
|
||||||
|
|
||||||
|
// One condition variable per column. Any time new information
|
||||||
|
// is added to the accumulation buffer the condition of the
|
||||||
|
// associated column is signaled
|
||||||
os_condvar_t accum_conds[MAX_COLUMNS];
|
os_condvar_t accum_conds[MAX_COLUMNS];
|
||||||
|
|
||||||
void start_workers(os_thread *workers);
|
// Counters that indicate how much information each column
|
||||||
void stop_workers(os_thread *workers);
|
// is storing. An integer value of N means N full frames have
|
||||||
|
// been accumulated. Lower resolution frames contribute lower
|
||||||
|
// values (half resolution weighs 0.25).
|
||||||
|
float accum_counts[MAX_COLUMNS];
|
||||||
|
|
||||||
void screenshot(void);
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
void parse_arguments_or_exit(int argc, char **argv, int *num_columns, int *init_scale, char **scene_file);
|
/// FUNCTION PROTOTYPES ///
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void start_workers(void);
|
||||||
|
void stop_workers(void);
|
||||||
|
|
||||||
|
bool quitting(void);
|
||||||
|
void screenshot(void);
|
||||||
|
void parse_arguments_or_exit(int argc, char **argv, int *num_columns, int *init_scale, char **scene_file);
|
||||||
|
|
||||||
|
Vector3 pixel(float x, float y, float aspect_ratio);
|
||||||
|
void update_frame(void);
|
||||||
|
float render_column(Vector3 *data, int scale_, int column_w, int column_i, int frame_w, int frame_h, uint64_t cached_generation);
|
||||||
|
void invalidate_accumulation(void);
|
||||||
|
|
||||||
|
os_threadreturn worker(void *arg);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// IMPLEMENTATION ///
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Resets the current frame and accumulation buffers and tells
|
||||||
|
// every worker to drop what they are doing and start again.
|
||||||
void invalidate_accumulation(void)
|
void invalidate_accumulation(void)
|
||||||
{
|
{
|
||||||
os_mutex_lock(&frame_mutex);
|
os_mutex_lock(&frame_mutex);
|
||||||
@@ -70,7 +118,7 @@ void invalidate_accumulation(void)
|
|||||||
os_mutex_unlock(&frame_mutex);
|
os_mutex_unlock(&frame_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 fresnelSchlick(float u, Vector3 f0)
|
Vector3 fresnel_schlick(float u, Vector3 f0)
|
||||||
{
|
{
|
||||||
return combine(f0, combine(vec_from_scalar(1.0), f0, 1, -1), 1, pow(1.0 - u, 5.0));
|
return combine(f0, combine(vec_from_scalar(1.0), f0, 1, -1), 1, pow(1.0 - u, 5.0));
|
||||||
}
|
}
|
||||||
@@ -138,7 +186,7 @@ Vector3 pixel(float x, float y, float aspect_ratio)
|
|||||||
Vector3 f0_dielectric = vec_from_scalar(0.16 * material.reflectance * material.reflectance);
|
Vector3 f0_dielectric = vec_from_scalar(0.16 * material.reflectance * material.reflectance);
|
||||||
Vector3 f0_metal = material.albedo;
|
Vector3 f0_metal = material.albedo;
|
||||||
Vector3 f0 = combine(f0_dielectric, f0_metal, (1 - material.metallic), material.metallic);
|
Vector3 f0 = combine(f0_dielectric, f0_metal, (1 - material.metallic), material.metallic);
|
||||||
Vector3 F = fresnelSchlick(NoV, f0);
|
Vector3 F = fresnel_schlick(NoV, f0);
|
||||||
|
|
||||||
Vector3 rand_dir = random_direction();
|
Vector3 rand_dir = random_direction();
|
||||||
if (dotv(rand_dir, hit.normal) < 0)
|
if (dotv(rand_dir, hit.normal) < 0)
|
||||||
@@ -176,28 +224,34 @@ Vector3 pixel(float x, float y, float aspect_ratio)
|
|||||||
|
|
||||||
float render_column(Vector3 *data, int scale_, int column_w, int column_i, int frame_w, int frame_h, uint64_t cached_generation)
|
float render_column(Vector3 *data, int scale_, int column_w, int column_i, int frame_w, int frame_h, uint64_t cached_generation)
|
||||||
{
|
{
|
||||||
|
// Since we're rendering at lower resolution, the weight of the
|
||||||
|
// pixels we produce is also reduced.
|
||||||
float scale2inv = 1.0f / (scale_ * scale_);
|
float scale2inv = 1.0f / (scale_ * scale_);
|
||||||
|
|
||||||
int column_x = column_w * column_i;
|
int column_x = column_w * column_i;
|
||||||
float aspect_ratio = (float) frame_w / frame_h;
|
float aspect_ratio = (float) frame_w / frame_h;
|
||||||
|
|
||||||
|
// Just lower resolution version of each variable
|
||||||
int lowres_frame_w = frame_w / scale_;
|
int lowres_frame_w = frame_w / scale_;
|
||||||
int lowres_frame_h = frame_h / scale_;
|
int lowres_frame_h = frame_h / scale_;
|
||||||
int lowres_column_w = column_w / scale_ + 1;
|
int lowres_column_w = column_w / scale_ + 1;
|
||||||
int lowres_column_x = column_x / scale_;
|
int lowres_column_x = column_x / scale_;
|
||||||
|
|
||||||
|
// Iterate over each low resolution pixel
|
||||||
for (int j = 0; j < lowres_frame_h; j++) {
|
for (int j = 0; j < lowres_frame_h; j++) {
|
||||||
for (int i = 0; i < lowres_column_w; i++) {
|
for (int i = 0; i < lowres_column_w; i++) {
|
||||||
|
|
||||||
float u = (float) (lowres_column_x + i) / (lowres_frame_w - 1);
|
float u = (float) (lowres_column_x + i) / (lowres_frame_w - 1);
|
||||||
float v = (float) j / (lowres_frame_h - 1);
|
float v = (float) j / (lowres_frame_h - 1);
|
||||||
u = 1 - u;
|
u = 1 - u;
|
||||||
v = 1 - v;
|
v = 1 - v;
|
||||||
|
|
||||||
|
// Now copy the value of the single low resolution
|
||||||
|
// pixel into a square of high resolution pixels
|
||||||
int tile_w = scale_;
|
int tile_w = scale_;
|
||||||
int tile_h = scale_;
|
int tile_h = scale_;
|
||||||
if (tile_w > column_w - i * scale_)
|
if (tile_w > column_w - i * scale_)
|
||||||
tile_w = column_w - i * scale_;
|
tile_w = column_w - i * scale_;
|
||||||
|
|
||||||
Vector3 color = pixel(u, v, aspect_ratio);
|
Vector3 color = pixel(u, v, aspect_ratio);
|
||||||
for (int g = 0; g < tile_h; g++)
|
for (int g = 0; g < tile_h; g++)
|
||||||
for (int t = 0; t < tile_w; t++) {
|
for (int t = 0; t < tile_w; t++) {
|
||||||
@@ -206,30 +260,55 @@ float render_column(Vector3 *data, int scale_, int column_w, int column_i, int f
|
|||||||
data[pixel_index] = scale(color, 1);
|
data[pixel_index] = scale(color, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// We are done calculating a row of pixels!
|
||||||
|
|
||||||
|
// If the frame has been invalidated we need to
|
||||||
|
// exit and try again as soon as possible
|
||||||
if (cached_generation != atomic_load(&accum_generation))
|
if (cached_generation != atomic_load(&accum_generation))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the weight of the current column
|
||||||
return scale2inv;
|
return scale2inv;
|
||||||
}
|
}
|
||||||
|
|
||||||
os_threadreturn worker(void *arg)
|
os_threadreturn worker(void *arg)
|
||||||
{
|
{
|
||||||
float column_data_weight = 0;
|
// How many information is contained in the column buffer
|
||||||
|
float column_data_weight = 0;
|
||||||
|
|
||||||
|
// The actual pixels
|
||||||
Vector3 *column_data = NULL;
|
Vector3 *column_data = NULL;
|
||||||
|
|
||||||
|
// The screen is divided in "num_columns" columns
|
||||||
int column_i = (int) arg;
|
int column_i = (int) arg;
|
||||||
int column_w = 0;
|
int column_w;
|
||||||
|
|
||||||
|
// Workers need to know the frame size while evaluating pixel
|
||||||
|
// values. Since the frame size may change at any time, threads
|
||||||
|
// cache their value.
|
||||||
int cached_frame_w;
|
int cached_frame_w;
|
||||||
int cached_frame_h;
|
int cached_frame_h;
|
||||||
|
|
||||||
|
// Generation counter of the frame buffer when the worker
|
||||||
|
// started producing a new frame. If the camera moves in the
|
||||||
|
// or something else causing the frame buffer to be reset, this
|
||||||
|
// will let the worker know the information needs to be thrown
|
||||||
|
// away.
|
||||||
uint64_t cached_generation;
|
uint64_t cached_generation;
|
||||||
|
|
||||||
|
// This value determines the resolution at which pixels are
|
||||||
|
// evaluated. For scale_=1 the image is full size. For scale_=2
|
||||||
|
// the image size is halved (along both axis). When a worker
|
||||||
|
// evaluates a frame it starts at the lowest resolution "init_scale"
|
||||||
|
// and after each succesfull paint it doubles the resolution
|
||||||
int scale_ = init_scale;
|
int scale_ = init_scale;
|
||||||
|
|
||||||
os_mutex_lock(&frame_mutex);
|
os_mutex_lock(&frame_mutex);
|
||||||
while (!workers_should_stop) {
|
while (!quitting()) {
|
||||||
|
|
||||||
|
// Cache data and check if we need to resize the column buffer
|
||||||
bool resize = false;
|
bool resize = false;
|
||||||
|
|
||||||
if (column_data == NULL || cached_generation != atomic_load(&accum_generation))
|
if (column_data == NULL || cached_generation != atomic_load(&accum_generation))
|
||||||
resize = true;
|
resize = true;
|
||||||
column_w = frame_w / num_columns;
|
column_w = frame_w / num_columns;
|
||||||
@@ -238,18 +317,24 @@ os_threadreturn worker(void *arg)
|
|||||||
cached_generation = atomic_load(&accum_generation);
|
cached_generation = atomic_load(&accum_generation);
|
||||||
os_mutex_unlock(&frame_mutex);
|
os_mutex_unlock(&frame_mutex);
|
||||||
|
|
||||||
|
// We need to resize
|
||||||
if (resize) {
|
if (resize) {
|
||||||
free(column_data);
|
free(column_data);
|
||||||
column_data = malloc(sizeof(Vector3) * column_w * cached_frame_h);
|
column_data = malloc(sizeof(Vector3) * column_w * cached_frame_h);
|
||||||
if (!column_data) abort();
|
if (!column_data) abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do the ray tracing
|
||||||
column_data_weight += render_column(column_data, scale_, column_w, column_i, cached_frame_w, cached_frame_h, cached_generation);
|
column_data_weight += render_column(column_data, scale_, column_w, column_i, cached_frame_w, cached_frame_h, cached_generation);
|
||||||
|
|
||||||
|
// Now we try publishing the changes
|
||||||
os_mutex_lock(&frame_mutex);
|
os_mutex_lock(&frame_mutex);
|
||||||
|
|
||||||
// Publish changes
|
|
||||||
if (cached_generation == atomic_load(&accum_generation)) {
|
if (cached_generation == atomic_load(&accum_generation)) {
|
||||||
|
// Frame didn't change its size while we were evaluating the column
|
||||||
|
|
||||||
|
// This loop basically copies the pixel colors from the column buffer to
|
||||||
|
// the frame buffer.
|
||||||
for (int j = 0; j < frame_h; j++)
|
for (int j = 0; j < frame_h; j++)
|
||||||
for (int i = 0; i < column_w; i++) {
|
for (int i = 0; i < column_w; i++) {
|
||||||
int column_x = column_w * column_i;
|
int column_x = column_w * column_i;
|
||||||
@@ -259,53 +344,77 @@ os_threadreturn worker(void *arg)
|
|||||||
assert(dst_index >= 0 && dst_index < cached_frame_w * cached_frame_h);
|
assert(dst_index >= 0 && dst_index < cached_frame_w * cached_frame_h);
|
||||||
accum[dst_index] = combine(accum[dst_index], column_data[src_index], 1, 1.0f / (scale_ * scale_));
|
accum[dst_index] = combine(accum[dst_index], column_data[src_index], 1, 1.0f / (scale_ * scale_));
|
||||||
}
|
}
|
||||||
os_condvar_signal(&accum_conds[column_i]);
|
|
||||||
accum_counts[column_i] += column_data_weight;
|
accum_counts[column_i] += column_data_weight;
|
||||||
|
|
||||||
|
// Let the main thread know there are new pixels
|
||||||
|
os_condvar_signal(&accum_conds[column_i]);
|
||||||
|
|
||||||
|
// We painted succesfully so we can render at double the resolution next time
|
||||||
if (scale_ > 1)
|
if (scale_ > 1)
|
||||||
scale_ >>= 1;
|
scale_ >>= 1;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// Data was invalidated. We need to go back and render at low res
|
||||||
scale_ = init_scale;
|
scale_ = init_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Either way we need to reset the column data now
|
||||||
column_data_weight = 0;
|
column_data_weight = 0;
|
||||||
}
|
}
|
||||||
os_mutex_unlock(&frame_mutex);
|
os_mutex_unlock(&frame_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_frame_texture(void)
|
void realloc_frame_buffer(void)
|
||||||
|
{
|
||||||
|
frame_w = get_screen_w();
|
||||||
|
frame_h = get_screen_h();
|
||||||
|
|
||||||
|
if (frame) free(frame);
|
||||||
|
if (accum) free(accum);
|
||||||
|
|
||||||
|
frame = malloc(sizeof(Vector3) * frame_w * frame_h);
|
||||||
|
if (!frame) {
|
||||||
|
printf("OUT OF MEMORY\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
accum = malloc(sizeof(Vector3) * frame_w * frame_h);
|
||||||
|
if (!accum) {
|
||||||
|
printf("OUT OF MEMORY\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_columns; i++)
|
||||||
|
accum_counts[i] = 0;
|
||||||
|
|
||||||
|
memset(accum, 0, sizeof(Vector3) * frame_w * frame_h);
|
||||||
|
memset(frame, 0, sizeof(Vector3) * frame_w * frame_h);
|
||||||
|
|
||||||
|
atomic_fetch_add(&accum_generation, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool frame_buffer_size_doesnt_match_window(void)
|
||||||
|
{
|
||||||
|
return frame_w != get_screen_w() || frame_h != get_screen_h();
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_frame(void)
|
||||||
{
|
{
|
||||||
os_mutex_lock(&frame_mutex);
|
os_mutex_lock(&frame_mutex);
|
||||||
|
|
||||||
if (frame_w != get_screen_w() || frame_h != get_screen_h()) {
|
if (frame_buffer_size_doesnt_match_window())
|
||||||
|
realloc_frame_buffer();
|
||||||
frame_w = get_screen_w();
|
|
||||||
frame_h = get_screen_h();
|
|
||||||
|
|
||||||
if (frame) free(frame);
|
|
||||||
if (accum) free(accum);
|
|
||||||
|
|
||||||
frame = malloc(sizeof(Vector3) * frame_w * frame_h);
|
|
||||||
if (!frame) { printf("OUT OF MEMORY\n"); abort(); }
|
|
||||||
|
|
||||||
accum = malloc(sizeof(Vector3) * frame_w * frame_h);
|
|
||||||
if (!accum) { printf("OUT OF MEMORY\n"); abort(); }
|
|
||||||
|
|
||||||
for (int i = 0; i < num_columns; i++)
|
|
||||||
accum_counts[i] = 0;
|
|
||||||
|
|
||||||
memset(accum, 0, sizeof(Vector3) * frame_w * frame_h);
|
|
||||||
memset(frame, 0, sizeof(Vector3) * frame_w * frame_h);
|
|
||||||
|
|
||||||
atomic_fetch_add(&accum_generation, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int column_w = frame_w / num_columns;
|
int column_w = frame_w / num_columns;
|
||||||
|
|
||||||
|
// Wait for the workers to produce a frame
|
||||||
|
// (each worker produces a column)
|
||||||
for (int i = 0; i < num_columns; i++) {
|
for (int i = 0; i < num_columns; i++) {
|
||||||
while (accum_counts[i] < 0.0001)
|
while (accum_counts[i] < 0.0001)
|
||||||
os_condvar_wait(&accum_conds[i], &frame_mutex, -1);
|
os_condvar_wait(&accum_conds[i], &frame_mutex, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy pixels from the accumulation buffer to the frame buffer
|
||||||
for (int j = 0; j < frame_h; j++)
|
for (int j = 0; j < frame_h; j++)
|
||||||
for (int i = 0; i < frame_w; i++) {
|
for (int i = 0; i < frame_w; i++) {
|
||||||
|
|
||||||
@@ -319,6 +428,7 @@ void update_frame_texture(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
move_frame_to_the_gpu(frame_w, frame_h, frame);
|
move_frame_to_the_gpu(frame_w, frame_h, frame);
|
||||||
|
|
||||||
os_mutex_unlock(&frame_mutex);
|
os_mutex_unlock(&frame_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,8 +452,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
startup_window_and_opengl_context_or_exit(2 * 640, 2 * 480, "Ray Tracing");
|
startup_window_and_opengl_context_or_exit(2 * 640, 2 * 480, "Ray Tracing");
|
||||||
|
|
||||||
os_thread workers[MAX_COLUMNS];
|
start_workers();
|
||||||
start_workers(workers);
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
@@ -391,11 +500,11 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
if (exit) break;
|
if (exit) break;
|
||||||
|
|
||||||
update_frame_texture();
|
update_frame();
|
||||||
draw_frame();
|
draw_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop_workers(workers);
|
stop_workers();
|
||||||
free_cubemap(&skybox);
|
free_cubemap(&skybox);
|
||||||
cleanup_window_and_opengl_context();
|
cleanup_window_and_opengl_context();
|
||||||
return 0;
|
return 0;
|
||||||
@@ -496,8 +605,22 @@ void screenshot(void)
|
|||||||
fprintf(stderr, "Took screenshot! (%s)\n", file);
|
fprintf(stderr, "Took screenshot! (%s)\n", file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_workers(os_thread *workers)
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// WORKER SYNCHRONIZATION ///
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static bool workers_should_stop;
|
||||||
|
os_thread workers[MAX_COLUMNS];
|
||||||
|
|
||||||
|
bool quitting(void)
|
||||||
{
|
{
|
||||||
|
return workers_should_stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_workers(void)
|
||||||
|
{
|
||||||
|
workers_should_stop = false;
|
||||||
|
|
||||||
os_mutex_create(&frame_mutex);
|
os_mutex_create(&frame_mutex);
|
||||||
|
|
||||||
for (int i = 0; i < num_columns; i++)
|
for (int i = 0; i < num_columns; i++)
|
||||||
@@ -507,7 +630,7 @@ void start_workers(os_thread *workers)
|
|||||||
os_thread_create(&workers[i], (void*) i, worker);
|
os_thread_create(&workers[i], (void*) i, worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop_workers(os_thread *workers)
|
void stop_workers(void)
|
||||||
{
|
{
|
||||||
os_mutex_lock(&frame_mutex);
|
os_mutex_lock(&frame_mutex);
|
||||||
workers_should_stop = true;
|
workers_should_stop = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user