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 { render_resolution: Resolution, viewport: Viewport, backbuffer: Backbuffer, surface_ptr: rawptr, vsync: bool, backend: rawptr, polygon_mode: Polygon_Mode, pipeline: Pipeline, active_camera: ^Camera, // NOTE: SS - Hardcoded to 1 active camera. Split-screen is likely not possible due to this. Fix(?). // Primitive meshes. default_cube_mesh, default_quad_mesh, default_sphere_mesh: Mesh, fullscreen_vertex_shader, fullscreen_fragment_shader: Shader, fullscreen_shader_program: Shader_Program, fullscreen_mesh: Mesh, } Polygon_Mode :: enum { Fill, Line, Point, } Resolution :: struct { width, height: u16, } Backbuffer :: struct { resolution: Resolution, } Viewport :: struct { x, y: u16, res: Resolution, } Aspect_Ratio_Type :: enum { // TODO: SS - Rename? Render, Backbuffer, Viewport, } get_aspect_ratio :: proc(renderer: ^Renderer, type: Aspect_Ratio_Type) -> f32 { assert(renderer != nil) res: Resolution switch type { case .Render: res = renderer.render_resolution case .Backbuffer: res = renderer.backbuffer.resolution case .Viewport: res = renderer.viewport.res } return f32(res.width) / f32(res.height) } create :: proc(render_resolution: Resolution, surface_ptr: rawptr) -> (^Renderer, bool) { if render_resolution.width == 0 || render_resolution.height == 0 { return nil, false } renderer := new(Renderer) renderer.render_resolution = render_resolution 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 } set_vsync(renderer, true) { // Create the default mesh-primitives. // default_cube_mesh, default_quad_mesh, default_sphere_mesh: Mesh, if m, ok := create_mesh(renderer, .Cube); ok { renderer.default_cube_mesh = m } if m, ok := create_mesh(renderer, .Quad); ok { renderer.default_quad_mesh = m } if m, ok := create_mesh(renderer, .Sphere); ok { renderer.default_sphere_mesh = m } } { // 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_backbuffer_resolution :: proc(renderer: ^Renderer, res: Resolution) { assert(renderer != nil) renderer.backbuffer.resolution = res } set_viewport :: proc { set_viewport_with_viewport, set_viewport_with_x_y_w_h, } set_viewport_with_viewport :: proc(renderer: ^Renderer, viewport: Viewport) { assert(renderer != nil) // log.infof("Setting viewport to %v:%v, %vx%v.", viewport.x, viewport.y, viewport.res.width, viewport.res.height) renderer.viewport = viewport when RENDER_BACKEND_OPENGL { opengl_viewport_changed(renderer) } } set_viewport_with_x_y_w_h :: proc(renderer: ^Renderer, x, y, width, height: u16) { assert(renderer != nil) set_viewport_with_viewport(renderer, Viewport { x, y, { width, height } }) } set_vsync :: proc(renderer: ^Renderer, on: bool) { assert(renderer != nil) if renderer.vsync == on { return } success := false when RENDER_BACKEND_OPENGL { success = opengl_set_vsync(renderer, on) } if success { renderer.vsync = on } } @(private) set_clear_color :: proc(renderer: ^Renderer, color: Color) { when RENDER_BACKEND_OPENGL { opengl_set_clear_color(renderer, color) } } @(private) clear_screen :: proc(renderer: ^Renderer, clear_color, clear_depth, scissor: bool) { when RENDER_BACKEND_OPENGL { opengl_clear_screen(renderer, clear_color, clear_depth, scissor) } } render_frame :: proc(renderer: ^Renderer, texture_to_present: ^Texture, clear_color: Color) { if renderer.active_camera == nil { fmt.printfln("No active camera!") return } camera_view_matrix, _ := get_camera_view_matrix(renderer.active_camera) 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], camera_view_matrix, camera_projection_matrix) } apply_polygon_mode(renderer, .Fill) if texture_to_present != nil { // Present. // Bind to the screen. bind_render_target(renderer, nil) // Disable depth apply_depth(renderer, false, false) compute_letterboxed_viewport :: proc(backbuffer: Resolution, render: Resolution) -> Viewport { bb_w, bb_h := expand_values(backbuffer) r_w, r_h := expand_values(render) bb_aspect := f32(bb_w) / f32(bb_h) r_aspect := f32(r_w) / f32(r_h) if bb_aspect > r_aspect { // Pillarbox. h := bb_h w := u16(f32(h) * r_aspect) x := (bb_w - w) / 2 return { x, 0, { w, h } } } else { // Letterbox. w := bb_w h := u16(f32(w) / r_aspect) y := (bb_h - h) / 2 return { 0, y, { w, h } } } } // Clear set_viewport(renderer, 0, 0, renderer.backbuffer.resolution.width, renderer.backbuffer.resolution.height) set_clear_color(renderer, RGB_Color{ 0, 0, 0 }) clear_screen(renderer, true, false, false) set_viewport(renderer, compute_letterboxed_viewport(renderer.backbuffer.resolution, renderer.render_resolution)) set_clear_color(renderer, clear_color) clear_screen(renderer, true, true, true) // Create a temporary Material. mat := Material { shader_program = &renderer.fullscreen_shader_program, textures = { 0 = texture_to_present, }, texture_count = 1, } apply_blend_mode(renderer, .Alpha) // Activate. activate_fullscreen_material(renderer, &mat) defer deactivate_fullscreen_material(renderer) // Draw. draw_mesh(&renderer.fullscreen_mesh) apply_blend_mode(renderer, .None) } when RENDER_BACKEND_OPENGL { opengl_swap_buffers(renderer) } } 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) defer bind_render_target(renderer, nil) apply_depth(renderer, false, 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) set_shader_uniforms(node.program, node.uniforms) mat: Material mat.shader_program = node.program for u in node.uniforms { if t, is_texture := u.(Uniform_Texture); is_texture { mat.textures[mat.texture_count] = t.value mat.texture_count += 1 } } activate_fullscreen_material(renderer, &mat) draw_mesh(&renderer.fullscreen_mesh) } destroy :: proc(renderer: ^Renderer) { delete_mesh(renderer, &renderer.default_cube_mesh) delete_mesh(renderer, &renderer.default_quad_mesh) delete_mesh(renderer, &renderer.default_sphere_mesh) when RENDER_BACKEND_OPENGL { opengl_destroy(renderer) } assert(renderer != nil) free(renderer) } @(private) activate_shader_program :: proc(program: ^Shader_Program) { when RENDER_BACKEND_OPENGL { opengl_activate_shader_program(program) } } @(private) activate_material :: proc(material: ^Material, model_matrix, view_matrix, projection_matrix: linalg.Matrix4x4f32, uv_scale: [2]f32 = { 1.0, 1.0 }) { assert(material != nil) p := material.shader_program activate_shader_program(p) set_shader_uniforms(material.shader_program, material.uniforms) 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) apply_cull_mode :: proc(renderer: ^Renderer, cull_mode: Cull_Mode) { when RENDER_BACKEND_OPENGL { opengl_set_cull_mode(renderer, cull_mode) } } @(private) sort_draw_commands :: proc(renderer: ^Renderer, pass: ^Scene_Pass) { // TODO: SS - Set the pass' 'renderer' variable here instead? 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) assert(material != nil) assert(material.shader_program != nil) activate_shader_program(material.shader_program) 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() } } @(private) apply_polygon_mode :: proc(renderer: ^Renderer, mode: Polygon_Mode) { assert(renderer != nil) when RENDER_BACKEND_OPENGL { opengl_apply_polygon_mode(renderer, mode) } }