import std::concat_strings; import std::print; import std::from_str; import std::free_string; import std::add_num_to_str; /////////////////// /// SDL externs /// /////////////////// // Helper struct for stack allocated const sized strings, because structs are // easier to create uninit than arrays. struct SDL_Window {} struct SDL_Renderer {} struct SDL_Texture {} struct SDL_Event { type: u32, reserved: [u8; 124] } struct SDL_FRect { x: f32, y: f32, w: f32, h: f32 } struct SDL_Rect { x: i32, y: i32, w: i32, h: i32 } extern fn SDL_malloc(size: u64) -> *u8; extern fn SDL_Init(flags: u32) -> bool; extern fn SDL_Quit(); extern fn SDL_CreateWindowAndRenderer(title: *char, width: i32, height: i32, flags: i32, window_out: &mut *SDL_Window, renderer_out: &mut *SDL_Renderer) -> bool; extern fn SDL_Delay(ms: u32); extern fn SDL_SetRenderDrawColor(renderer: *SDL_Renderer, r: u8, g: u8, b: u8, a: u8); extern fn SDL_RenderClear(renderer: *SDL_Renderer); extern fn SDL_RenderPresent(renderer: *SDL_Renderer); extern fn SDL_HasEvent(event_type: u32) -> bool; extern fn SDL_PollEvent(event: &mut SDL_Event) -> bool; extern fn SDL_PumpEvents(); extern fn SDL_FlushEvents(min_type: u32, max_type: u32); extern fn SDL_GetTicks() -> u64; extern fn SDL_SetWindowTitle(window: *SDL_Window, title: *char) -> bool; extern fn SDL_CreateTexture(renderer: *SDL_Renderer, pixel_format: u32, texture_access: u32, width: u32, height: u32) -> *SDL_Texture; extern fn SDL_RenderTexture(renderer: *SDL_Renderer, texture: *SDL_Texture, srcfrect: &SDL_FRect, dstfrect: &SDL_FRect) -> bool; extern fn SDL_UpdateTexture(texture: *SDL_Texture, rect: &SDL_Rect, pixels: *u8, pitch: u32) -> bool; extern fn SDL_GetError() -> *char; extern fn SDL_GetWindowSize(window: *SDL_Window, w: &mut i32, h: &mut i32) -> bool; extern fn SDL_rand(max_exclusive: u32) -> u32; extern fn SDL_SetTextureScaleMode(texture: *SDL_Texture, scale_mode: i32) -> bool; extern fn SDL_sqrtf(value: f32) -> f32; extern fn SDL_randf() -> f32; extern fn SDL_powf(value: f32, power: f32) -> f32; // SDL error reporting helper fn print_sdl_error(context: *char) { let mut message = from_str(context); let delim = from_str(": "); concat_strings(&mut message, delim); free_string(&delim); let error_msg = from_str(SDL_GetError()); concat_strings(&mut message, error_msg); free_string(&error_msg); print(message); free_string(&message); } ///////////////////////////////// /// Main setup and frame loop /// ///////////////////////////////// struct GameState { renderer: *SDL_Renderer, window: *SDL_Window, render_texture: *SDL_Texture, frame_counter: u32, last_fps_reset: u64, pixels: *u8, pixels_w: u32, pixels_h: u32, pixels_bpp: u32, } fn main() -> i32 { let SDL_INIT_VIDEO = 32; let SDL_WINDOW_RESIZABLE = 32; let SDL_PIXELFORMAT_RGBA8888 = 373694468; let SDL_PIXELFORMAT_ABGR8888 = 376840196; let SDL_PIXELFORMAT_RGB24 = 386930691; let SDL_PIXELFORMAT_BGR24 = 390076419; let SDL_PIXELFORMAT_RGB96_FLOAT = 454057996; let SDL_PIXELFORMAT_BGR96_FLOAT = 457203724; let SDL_TEXTUREACCESS_STREAMING = 1; let SDL_SCALEMODE_NEAREST = 0; let SDL_SCALEMODE_LINEAR = 1; let SDL_SCALEMODE_PIXELART = 2; let init_success = SDL_Init(SDL_INIT_VIDEO); if init_success == false { print_sdl_error("SDL init failed"); return 1; } let mut window = SDL_malloc(1) as *SDL_Window; let mut renderer = SDL_malloc(1) as *SDL_Renderer; let gfx_init_success = SDL_CreateWindowAndRenderer( "graphical reid program", 640, 480, SDL_WINDOW_RESIZABLE, &mut window, &mut renderer ); if gfx_init_success == false { print_sdl_error("SDL renderer and window creation failed"); return 1; } let width = 320; let height = 240; let bpp = 4; let render_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, width, height); SDL_SetTextureScaleMode(render_texture, SDL_SCALEMODE_NEAREST); let pixels_len = (width * height * bpp) as u64; let pixels = SDL_malloc(pixels_len); let mut game_state = GameState { renderer: renderer, window: window, render_texture: render_texture, frame_counter: 0, last_fps_reset: 0, pixels: pixels, pixels_w: width, pixels_h: height, pixels_bpp: bpp, }; while frame_loop(&mut game_state) {} SDL_Quit(); return 0; } fn frame_loop(game_state: &mut GameState) -> bool { let mut event = SDL_Event {}; while (SDL_PollEvent(&mut event)) { if event.type == 256 { // SDL_EVENT_QUIT return false; } } let mut screen_width = 0; let mut screen_height = 0; SDL_GetWindowSize(*game_state.window, &mut screen_width, &mut screen_height); let renderer = *game_state.renderer; SDL_SetRenderDrawColor(renderer, 0, 50, 90, 255); SDL_RenderClear(renderer); let w = *game_state.pixels_w; let h = *game_state.pixels_h; let bpp = *game_state.pixels_bpp; for y in 0..h { for x in 0..w { render_pixel(x, y, game_state); } } let texture_area = SDL_Rect { x: 0, y: 0, w: w as i32, h: h as i32 }; if SDL_UpdateTexture(*game_state.render_texture, &texture_area, *game_state.pixels as *u8, bpp * w) == false { print_sdl_error("UpdateTexture error"); } let src = SDL_FRect { x: 0.0, y: 0.0, w: w as f32, h: h as f32 }; let aspect_ratio = src.w / src.h; let scaled_width = screen_height as f32 * aspect_ratio; let dst = SDL_FRect { x: (screen_width as f32 - scaled_width) / 2.0, y: 0.0, w: scaled_width, h: screen_height as f32 }; if SDL_RenderTexture(renderer, *game_state.render_texture, &src, &dst) == false { print_sdl_error("RenderTexture error"); } SDL_RenderPresent(renderer); SDL_Delay(1); *game_state.frame_counter = *game_state.frame_counter + 1; let t = SDL_GetTicks(); if (t - *game_state.last_fps_reset) >= 1000 { let mut title = from_str("graphical reid program "); add_num_to_str(&mut title, *game_state.frame_counter as u64); let fps_unit = from_str(" fps"); concat_strings(&mut title, fps_unit); free_string(&fps_unit); SDL_SetWindowTitle(*game_state.window, title.inner); free_string(&title); *game_state.frame_counter = 0; *game_state.last_fps_reset = t; } return true; } fn render_pixel(x: u32, y: u32, game_state: &mut GameState) { let w = *game_state.pixels_w; let h = *game_state.pixels_h; let bpp = *game_state.pixels_bpp; let samples = 1; let old_sample_weight = 0.75; let new_sample_weight = 0.25 / samples as f32; let mut rgb = vec_mul_scalar(old_sample_weight, [ srgb_to_linear(*game_state.pixels[(x + y * w) * bpp + 0]), srgb_to_linear(*game_state.pixels[(x + y * w) * bpp + 1]), srgb_to_linear(*game_state.pixels[(x + y * w) * bpp + 2]) ]); for sample in 0..samples { rgb = vec_add(rgb, vec_mul_scalar(new_sample_weight, shade(x, y, *game_state.frame_counter, w, h))); } *game_state.pixels[(x + y * w) * bpp + 0] = linear_to_srgb(rgb[0]); *game_state.pixels[(x + y * w) * bpp + 1] = linear_to_srgb(rgb[1]); *game_state.pixels[(x + y * w) * bpp + 2] = linear_to_srgb(rgb[2]); *game_state.pixels[(x + y * w) * bpp + 3] = 255; } ///////////////// /// Rendering /// ///////////////// struct Ray { origin: [f32; 3], direction: [f32; 3], } struct Material { // 0 = lambertian diffuse // 1 = mirror type: u32, // Generally the "color" of the surface (linear factors of how much of each // color channel this surface does not absorb), but the idea is that the // type governs what this means. linear_color: [f32; 3], } struct Hit { hit: bool, front_face: bool, distance: f32, normal: [f32; 3], position: [f32; 3], material: Material, } struct Sphere { center: [f32; 3], radius: f32, material: Material, } fn shade(x: u32, y: u32, t: u32, w: u32, h: u32) -> [f32; 3] { let jitter_x = SDL_randf() - 0.5; let jitter_y = SDL_randf() - 0.5; let pixel_scale = 1.0 / h as f32; let pixel_pos = [ (x as f32 + jitter_x) * pixel_scale, 1.0 - (y as f32 + jitter_y) * pixel_scale, 0.0 - 1.0 ]; let camera_pos = [w as f32 * 0.5f32 * pixel_scale, h as f32 * 0.5f32 * pixel_scale, 0.0f32]; let dir = vec_normalize(vec_sub(pixel_pos, camera_pos)); let ray = Ray { origin: camera_pos, direction: dir }; let beige_lambertian = Material { type: 0, linear_color: [0.3, 0.2, 0.1] }; let green_lambertian = Material { type: 0, linear_color: [0.1, 0.5, 0.06] }; let greenish_mirror = Material { type: 1, linear_color: [0.9, 1.0, 0.95] }; let spheres = [ // Ground Sphere { center: vec_sub(camera_pos, [0.0, 100001.0, 0.0]), radius: 100000.0, material: beige_lambertian }, // Centered unit sphere Sphere { center: vec_add(camera_pos, [0.0, 0.0, 0.0 - 5.0]), radius: 1.0, material: green_lambertian }, // The unit sphere on the right Sphere { center: vec_add(camera_pos, [2.0, 0.0, 0.0 - 6.0]), radius: 1.0, material: greenish_mirror } ]; return shade_world(ray, &spheres, 3); } fn shade_world(ray: Ray, spheres: &[Sphere; 3], bounces_left: u8) -> [f32; 3] { if bounces_left == 0 { return [0.0, 0.0, 0.0]; } let ray_distance = 100.0; let mut closest_hit = Hit { hit: false, front_face: false, distance: ray_distance }; for i in 0..3 { let sphere_hit = ray_sphere_closest_hit(ray, *spheres[i], [0.001, closest_hit.distance]); if sphere_hit.hit { closest_hit = sphere_hit; } } if closest_hit.hit { //return vec_mul_scalar(0.5, vec_add(closest_hit.normal, [1.0, 1.0, 1.0])); // normal //return vec_mul_scalar(closest_hit.distance / 10.0, [1.0, 1.0, 1.0]); // depth if closest_hit.material.type == 0 { let bounce_dir = vec_normalize(vec_add(closest_hit.normal, random_unit_vec())); let bounce_ray = Ray { origin: closest_hit.position, direction: bounce_dir }; return vec_mul_componentwise( closest_hit.material.linear_color, shade_world(bounce_ray, spheres, bounces_left - 1) ); } else if closest_hit.material.type == 1 { let bounce_dir = vec_reflect(ray.direction, closest_hit.normal); let bounce_ray = Ray { origin: closest_hit.position, direction: bounce_dir }; return vec_mul_componentwise( closest_hit.material.linear_color, shade_world(bounce_ray, spheres, bounces_left - 1) ); } else { return [1.0, 0.0, 1.0]; } } return shade_sky(ray); } fn shade_sky(ray: Ray) -> [f32; 3] { let a = 0.5 * (ray.direction[1] + 1.0); return vec_add( vec_mul_scalar(1.0 - a, [1.0, 1.0, 1.0]), vec_mul_scalar(a, [0.5, 0.7, 1.0]) ); } // Returns the distance from the ray origin to the sphere, or -1.0 if the ray doesn't hit. fn ray_sphere_closest_hit(ray: Ray, sphere: Sphere, interval: [f32; 2]) -> Hit { let to_sphere = vec_sub(sphere.center, ray.origin); let h = vec_dot(ray.direction, to_sphere); let c = vec_length_squared(to_sphere) - sphere.radius * sphere.radius; let discriminant = h * h - c; if discriminant < 0.0 { return Hit { hit: false }; } let discriminant_sqrt = SDL_sqrtf(discriminant); let mut distance = h - discriminant_sqrt; if interval_surrounds(interval, distance) == false { distance = h - discriminant_sqrt; if interval_surrounds(interval, distance) == false { return Hit { hit: false }; } } let hit_position = vec_add(ray.origin, vec_mul_scalar(distance, ray.direction)); let mut front_face = true; let mut normal = vec_normalize(vec_sub(hit_position, sphere.center)); if vec_dot(normal, ray.direction) > 0.0 { normal = vec_mul_scalar(0.0 - 1.0, normal); front_face = false; } return Hit { hit: true, front_face: front_face, distance: distance, normal: normal, position: hit_position, material: sphere.material, }; } ////////////////// /// Other math /// ////////////////// fn clamp(min: f32, max: f32, value: f32) -> f32 { if value > max { return max; } if value < min { return min; } return value; } fn abs(f: f32) -> f32 { if f < 0.0 { return f * (0.0 - 1.0); } return f; } fn vec_add(lhs: [f32; 3], rhs: [f32; 3]) -> [f32; 3] { return [lhs[0] + rhs[0], lhs[1] + rhs[1], lhs[2] + rhs[2]]; } fn vec_sub(lhs: [f32; 3], rhs: [f32; 3]) -> [f32; 3] { return [lhs[0] - rhs[0], lhs[1] - rhs[1], lhs[2] - rhs[2]]; } fn vec_dot(lhs: [f32; 3], rhs: [f32; 3]) -> f32 { return lhs[0] * rhs[0] + lhs[1] * rhs[1] + lhs[2] * rhs[2]; } fn vec_mul_componentwise(lhs: [f32; 3], rhs: [f32; 3]) -> [f32; 3] { return [lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2]]; } fn vec_mul_scalar(lhs: f32, rhs: [f32; 3]) -> [f32; 3] { return [lhs * rhs[0], lhs * rhs[1], lhs * rhs[2]]; } fn vec_normalize(v: [f32; 3]) -> [f32; 3] { let len_reciprocal = 1.0f32 / SDL_sqrtf(vec_length_squared(v)); return vec_mul_scalar(len_reciprocal, v); } fn vec_length_squared(v: [f32; 3]) -> f32 { return v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; } fn vec_abs(v: [f32; 3]) -> [f32; 3] { return [abs(v[0]), abs(v[1]), abs(v[2])]; } fn vec_reflect(direction: [f32; 3], normal: [f32; 3]) -> [f32; 3] { return vec_sub(direction, vec_mul_scalar(2.0f32 * vec_dot(direction, normal), normal)); } fn interval_surrounds(interval: [f32; 2], value: f32) -> bool { return (interval[0] < value) && (value < interval[1]); } fn random_unit_vec() -> [f32; 3] { let mut point = [ SDL_randf() * 2.0f32 - 1.0f32, SDL_randf() * 2.0f32 - 1.0f32, SDL_randf() * 2.0f32 - 1.0f32 ]; let mut lensq = vec_length_squared(point); while lensq > 1.0 { point = [ SDL_randf() * 2.0f32 - 1.0f32, SDL_randf() * 2.0f32 - 1.0f32, SDL_randf() * 2.0f32 - 1.0f32 ]; lensq = vec_length_squared(point); } let len_reciprocal = 1.0f32 / SDL_sqrtf(lensq); return vec_mul_scalar(len_reciprocal, point); } fn random_unit_vec_on_hemi(normal: [f32; 3]) -> [f32; 3] { let rand_vec = random_unit_vec(); if vec_dot(rand_vec, normal) < 0.0f32 { return vec_mul_scalar(0.0f32 - 1.0f32, rand_vec); } return rand_vec; } fn linear_to_srgb(linear: f32) -> u8 { let mut floating_srgb = 0.0; if linear <= 0.0031308f32 { floating_srgb = 12.92f32 * linear; } else { floating_srgb = SDL_powf(linear as f32, 1.0 / 2.4) * 1.055f32 - 0.055f32; } let clamped = clamp(0.0, 1.0, floating_srgb); return (clamped * 255.999) as u8; } fn srgb_to_linear(srgb: u8) -> f32 { let floating_srgb = srgb as f32 / 255.0; if floating_srgb <= 0.04045f32 { return floating_srgb / 12.92f32; } return SDL_powf((floating_srgb as f32 + 0.055) / 1.055, 2.4); }