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, // NOTE: SS - Hardcoded to 1 active camera. Split-screen is likely not possible due to this. Fix(?). fullscreen_vertex_shader, fullscreen_fragment_shader: Shader, fullscreen_shader_program: Shader_Program, fullscreen_mesh: Mesh, } 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) } Color :: union { RGB_Color, RGBA_Color, } 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 } { // Create the fullscreen shaders, material and mesh. fs_vertex_shader, fs_vertex_shader_ok := create_shader(renderer, .Vertex, "fs_vertex.glsl") assert(fs_vertex_shader_ok) fs_frag_shader, fs_frag_shader_ok := create_shader(renderer, .Fragment, "fs_frag.glsl") assert(fs_frag_shader_ok) fs_program, fs_program_ok := create_shader_program(renderer, &fs_vertex_shader, &fs_frag_shader) assert(fs_program_ok) fs_quad_mesh, fs_quad_mesh_created := create_mesh( renderer = renderer, layout = { { "position", 2, size_of(f32), }, }, vertices = []f32 { -1, -1, 1, -1, 1, 1, -1, 1, }, indices = []u32 { 0, 1, 2, 0, 2, 3, } ) assert(fs_quad_mesh_created) renderer.fullscreen_vertex_shader = fs_vertex_shader renderer.fullscreen_fragment_shader = fs_frag_shader renderer.fullscreen_shader_program = fs_program renderer.fullscreen_mesh = fs_quad_mesh } 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: 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, texture_to_present: ^Texture, clear_color: Color) { 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) for i in 0 ..< renderer.pipeline.amount_of_passes { execute_pass(renderer, renderer.pipeline.passes[i], view_matrix, projection_matrix) } if texture_to_present != nil { // Present. // Bind to the screen. bind_render_target(renderer, nil) // Disable depth apply_depth(renderer, false, false) // Clear set_clear_color(renderer, clear_color) clear_screen(renderer, true, true) // Create a temporary Material. mat := Material { shader_program = &renderer.fullscreen_shader_program, textures = { 0 = texture_to_present, }, texture_count = 1, } // Activate. activate_fullscreen_material(renderer, &mat) defer deactivate_fullscreen_material(renderer) // Draw. draw_mesh(&renderer.fullscreen_mesh) } when RENDER_BACKEND_OPENGL { opengl_swap_buffers(renderer) } } execute_pass :: proc(renderer: ^Renderer, pass: ^Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) { // fmt.printfln("Executing pass '%v'.", pass.name) assert(renderer != nil) assert(pass != nil) switch &t in &pass.type { case Scene_Pass: { assert(t.output_rt != nil) bind_render_target(renderer, t.output_rt) should_write_depth := t.output_rt.depth_texture != nil should_test_depth := should_write_depth should_clear_depth := should_write_depth should_clear_color := true set_clear_color(renderer, RGBA_Color { 0, 0, 0, 0 }) clear_screen(renderer, should_clear_color, should_clear_depth) apply_depth(renderer, should_test_depth, should_write_depth) apply_blend_mode(renderer, t.blend_mode) sort_draw_commands(renderer, &t) for &dc in &t.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) } // Clear the pass' draw-commands. clear(&t.draw_commands) // TODO: SS - "Deactivate" the pass? } case Post_Processing_Pass: { // Execute the post-processing nodes. for &pp in &t.post_processing_nodes { execute_post_processing_node(renderer, &pp, view_matrix, projection_matrix) } // TODO: SS - "Deactivate" the pass? } } } execute_post_processing_node :: proc(renderer: ^Renderer, node: ^Post_Processing_Node, view_matrix, projection_matrix: linalg.Matrix4x4f32) { assert(renderer != nil) assert(node != nil) assert(node.program != nil) assert(node.output != nil) assert(node.output != nil) bind_render_target(renderer, node.output) apply_depth(renderer, false, false) clear_screen(renderer, true, false) // fmt.printfln("TODO: SS - Execute post-processing node '%v' (VS: '%v', FS: '%v').", "NAME", node.program.vertex_shader.path, node.program.fragment_shader.path) t: [MATERIAL_MAX_TEXTURES]^Texture t_count := u8(0) for input_texture, i in node.input { if i > MATERIAL_MAX_TEXTURES { break } set_shader_value(node.program, input_texture, u8(i)) t[i] = input_texture t_count += 1 } mat := Material { shader_program = node.program, textures = t, texture_count = t_count, } activate_fullscreen_material(renderer, &mat) draw_mesh(&renderer.fullscreen_mesh) } set_shader_value :: proc { set_shader_value_texture, } set_shader_value_texture :: proc(program: ^Shader_Program, value: ^Texture, index: u8) { assert(program != nil) assert(value != nil) when RENDER_BACKEND_OPENGL { opengl_set_shader_value_texture(program, value, index) } } 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) 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_render_target :: proc(renderer: ^Renderer, rt: ^Render_Target) { when RENDER_BACKEND_OPENGL { opengl_bind_render_target(renderer, rt) } } @(private) activate_fullscreen_material :: proc(renderer: ^Renderer, material: ^Material) { // TODO: SS - Maybe remove. assert(renderer != nil) when RENDER_BACKEND_OPENGL { opengl_activate_fullscreen_material(material) } } @(private) deactivate_fullscreen_material :: proc(renderer: ^Renderer) { // TODO: SS - Maybe remove. assert(renderer != nil) when RENDER_BACKEND_OPENGL { opengl_deactivate_fullscreen_material() } }