package renderer import "core:slice" import "core:sort" import "core:math/linalg" import "core:fmt" import "core:log" RENDER_BACKEND_OPENGL :: #config(RENDER_BACKEND_OPENGL, false) RENDER_BACKEND_VULKAN :: #config(RENDER_BACKEND_VULKAN, false) RENDER_BACKEND_DIRECTX11 :: #config(RENDER_BACKEND_DIRECTX11, false) RENDER_BACKEND_METAL :: #config(RENDER_BACKEND_METAL, false) Renderer :: struct { viewport: Viewport, surface_ptr: rawptr, backend: rawptr, pipeline: Pipeline, active_camera: ^Camera, } Viewport :: struct { x, y, width, height: u16, } get_aspect_ratio :: proc(renderer: ^Renderer) -> f32 { assert(renderer != nil) viewport := &renderer.viewport return f32(viewport.width) / f32(viewport.height) } RGB_Color :: [3]u8 RGBA_Color :: [4]u8 create :: proc(surface_ptr: rawptr) -> (^Renderer, bool) { renderer := new(Renderer) renderer.surface_ptr = surface_ptr when RENDER_BACKEND_OPENGL { if !opengl_init(renderer) { fmt.printfln("Failed to initialize OpenGL.") destroy(renderer) return nil, false } } else { destroy(renderer) fmt.printfln("Unhandled backend or no backend selected.") return nil, false } return renderer, true } set_viewport :: proc(renderer: ^Renderer, x, y, width, height: u16) { log.infof("Setting viewport to %v:%v, %vx%v.", x, y, width, height) renderer.viewport = { x = x, y = y, width = width, height = height, } when RENDER_BACKEND_OPENGL { opengl_viewport_changed(renderer) } } @(private="file") set_clear_color :: proc(renderer: ^Renderer, color: RGB_Color) { when RENDER_BACKEND_OPENGL { opengl_set_clear_color(renderer, color) } } @(private="file") clear_screen :: proc(renderer: ^Renderer, clear_color: bool, clear_depth: bool) { when RENDER_BACKEND_OPENGL { opengl_clear_screen(renderer, clear_color, clear_depth) } } render_frame :: proc(renderer: ^Renderer) { if renderer.active_camera == nil { fmt.printfln("No active camera!") return } view_matrix, _ := get_camera_view_matrix(renderer.active_camera) projection_matrix, _ := get_camera_projection_matrix(renderer, renderer.active_camera) pipeline := &renderer.pipeline for i in 0 ..< pipeline.amount_of_scene_passes { execute_scene_pass(renderer, pipeline.scene_passes[i], view_matrix, projection_matrix) } for i in 0 ..< pipeline.amount_of_post_processing_passes { execute_post_processing_pass(renderer, pipeline.post_processing_passes[i], view_matrix, projection_matrix) } bind_render_target(renderer, nil) // Det görs ju här.. if pipeline.fullscreen_material != nil { if pipeline.fullscreen_material.texture != nil { activate_fullscreen_material(renderer) if pipeline.fullscreen_mesh != nil { draw_mesh(pipeline.fullscreen_mesh) } else { log.warn("Renderer is missing a fullscreen-mesh.") } deactivate_fullscreen_material(renderer) } else { log.warn("Fullscreen-material does not have a texture.") } } else { log.warn("Renderer is missing a fullscreen-material.") } when RENDER_BACKEND_OPENGL { opengl_swap_buffers(renderer) } } execute_scene_pass :: proc(renderer: ^Renderer, pass: ^Scene_Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) { // fmt.printfln("Rendering pass '%v' to output_rt %v.", pass.name, pass.output_rt) // TODO: SS - Use 'pass.input_textures' assert(pass.output_rt != nil) bind_render_target(renderer, pass.output_rt) should_write_depth := pass.output_rt.depth_texture != nil should_test_depth := should_write_depth should_clear_depth := should_write_depth cc, has_clear_color := pass.clear_color.? if has_clear_color { set_clear_color(renderer, cc) } should_clear_color := has_clear_color && pass.output_rt.color_texture != nil clear_screen(renderer, should_clear_color, should_clear_depth) apply_depth(renderer, should_test_depth, should_write_depth) apply_blend_mode(renderer, pass.blend_mode) sort_draw_commands(renderer, pass) for &dc in &pass.draw_commands { // TODO: SS - Don't think we need the address of the draw-commands. model_matrix := linalg.identity(linalg.Matrix4x4f32) // Translate. translation := linalg.matrix4_translate(dc.position) // Rotate. rot_x := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.x), [3]f32 { 1, 0, 0 }) rot_y := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.y), [3]f32 { 0, 1, 0 }) rot_z := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.z), [3]f32 { 0, 0, 1 }) rotation := rot_z * rot_y * rot_x // Scale. scale := linalg.matrix4_scale(dc.scale) model_matrix *= translation * rotation * scale activate_material(&dc.material, model_matrix, view_matrix, projection_matrix) draw_mesh(&dc.mesh) } // TODO: SS - "Deactivate" the pass. // Clear the pass' draw-commands. clear(&pass.draw_commands) } execute_post_processing_pass :: proc(renderer: ^Renderer, pass: ^Post_Processing_Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) { fmt.printfln("TODO: SS - Execute post-processing pass '%v'.", pass.name) } destroy :: proc(renderer: ^Renderer) { when RENDER_BACKEND_OPENGL { opengl_destroy(renderer) } assert(renderer != nil) free(renderer) } @(private) activate_material :: proc(material: ^Material, model_matrix, view_matrix, projection_matrix: linalg.Matrix4x4f32) { assert(material != nil) when RENDER_BACKEND_OPENGL { opengl_activate_material(material, model_matrix, view_matrix, projection_matrix) } } @(private) activate_fullscreen_material :: proc(renderer: ^Renderer) { assert(renderer != nil) mat := renderer.pipeline.fullscreen_material when RENDER_BACKEND_OPENGL { opengl_activate_fullscreen_material(mat) } } @(private) deactivate_fullscreen_material :: proc(renderer: ^Renderer) { assert(renderer != nil) when RENDER_BACKEND_OPENGL { opengl_deactivate_fullscreen_material() } } @(private) draw_mesh :: proc(mesh: ^Mesh) { assert(mesh != nil) when RENDER_BACKEND_OPENGL { opengl_draw_mesh(mesh) } } @(private) apply_depth :: proc(renderer: ^Renderer, test_depth, write_depth: bool) { when RENDER_BACKEND_OPENGL { opengl_apply_depth(renderer, test_depth, write_depth) } } @(private) apply_blend_mode :: proc(renderer: ^Renderer, blend_mode: Blend_Mode) { table := BLEND_FACTOR_TABLE when RENDER_BACKEND_OPENGL { opengl_set_blending(renderer, blend_mode != .None, table[blend_mode]) } } @(private) sort_draw_commands :: proc(renderer: ^Renderer, pass: ^Scene_Pass) { switch pass.sort_mode { case .None: {} case .Back_To_Front: { slice.sort_by( pass.draw_commands[:], proc(i, j: Draw_Command) -> bool { assert(i.renderer != nil) assert(j.renderer != nil) assert(i.renderer == j.renderer) active_camera := get_active_camera(i.renderer) if active_camera == nil { return false } i_dist := distance_to_camera(active_camera, i.position) j_dist := distance_to_camera(active_camera, j.position) return i_dist > j_dist } ) } case .Front_To_Back: { slice.sort_by( pass.draw_commands[:], proc(i, j: Draw_Command) -> bool { assert(i.renderer != nil) assert(j.renderer != nil) assert(i.renderer == j.renderer) active_camera := get_active_camera(i.renderer) if active_camera == nil { return false } i_dist := distance_to_camera(active_camera, i.position) j_dist := distance_to_camera(active_camera, j.position) return i_dist < j_dist } ) } } } distance_to_camera :: proc(camera: ^Camera, position: [3]f32) -> f32 { assert(camera != nil) return linalg.distance(camera.position, position) } @(private) bind_framebuffer :: proc(renderer: ^Renderer, rt: ^Render_Target) -> bool { // when RENDER_BACKEND_OPENGL { // return opengl_bind_framebuffer(renderer, rt) // } return false } @(private) bind_depth_texture :: proc(renderer: ^Renderer, rt: ^Render_Target) -> bool { // when RENDER_BACKEND_OPENGL { // return opengl_bind_depth_texture(renderer, rt) // } return false } @(private) bind_render_target :: proc(renderer: ^Renderer, rt: ^Render_Target) { when RENDER_BACKEND_OPENGL { opengl_bind_render_target(renderer, rt) } }