diff --git a/camera.odin b/camera.odin index 980245f..edc7c26 100644 --- a/camera.odin +++ b/camera.odin @@ -121,7 +121,7 @@ get_camera_projection_matrix :: proc(renderer: ^Renderer, camera: ^Camera) -> (l case Camera_Perspective: { projection_matrix *= linalg.matrix4_perspective( linalg.to_radians(f32(t.fov_degrees)), - get_aspect_ratio(renderer), + get_aspect_ratio(renderer, .Render), camera.near, camera.far, flip_z_axis = true, @@ -129,7 +129,7 @@ get_camera_projection_matrix :: proc(renderer: ^Renderer, camera: ^Camera) -> (l } case Camera_Orthographic: { half_h := t.height / 2 - half_w := half_h * get_aspect_ratio(renderer) + half_w := half_h * get_aspect_ratio(renderer, .Render) projection_matrix *= linalg.matrix_ortho3d( -half_w, half_w, diff --git a/mesh.odin b/mesh.odin index baf6ad1..e9d3d6a 100644 --- a/mesh.odin +++ b/mesh.odin @@ -161,6 +161,8 @@ create_mesh_from_primitive :: proc(renderer: ^Renderer, primitive_mesh_type: Pri vertices := make([dynamic]f32, 0) indices := make([dynamic]u32, 0) + defer delete(vertices) + defer delete(indices) for y in 0..=Y_SEGMENTS { y_segment := f32(y) / f32(Y_SEGMENTS) @@ -233,4 +235,13 @@ create_mesh_from_primitive :: proc(renderer: ^Renderer, primitive_mesh_type: Pri } return {}, false +} + +delete_mesh :: proc(renderer: ^Renderer, mesh: ^Mesh) { + assert(renderer != nil) + assert(mesh != nil) + + when RENDER_BACKEND_OPENGL { + opengl_delete_mesh(renderer, mesh) + } } \ No newline at end of file diff --git a/pass.odin b/pass.odin index 757ca6d..9b5d997 100644 --- a/pass.odin +++ b/pass.odin @@ -243,10 +243,12 @@ delete_pass :: proc(pass: ^Pass) { } @(private) delete_scene_pass :: proc(pass: ^Scene_Pass) { + assert(pass.draw_commands != nil) delete(pass.draw_commands) } @(private) delete_post_processing_pass :: proc(pass: ^Post_Processing_Pass) { + assert(pass.post_processing_nodes != nil) delete(pass.post_processing_nodes) } @@ -294,9 +296,10 @@ execute_pass :: proc(renderer: ^Renderer, pass: ^Pass, view_matrix, projection_m should_clear_depth := should_write_depth should_clear_color := true + should_scissor :: false set_clear_color(renderer, RGBA_Color { 0, 0, 0, 0 }) - - clear_screen(renderer, should_clear_color, should_clear_depth) + clear_screen(renderer, should_clear_color, should_clear_depth, should_scissor) + apply_depth(renderer, should_test_depth, should_write_depth) apply_blend_mode(renderer, t.blend_mode) diff --git a/pipeline.odin b/pipeline.odin index 577d3c1..789ec59 100644 --- a/pipeline.odin +++ b/pipeline.odin @@ -4,7 +4,7 @@ import "core:mem" import "core:log" import "core:fmt" -PIPELINE_MAX_PASSES :: 8 +PIPELINE_MAX_PASSES :: 16 Pipeline :: struct { passes: [PIPELINE_MAX_PASSES]^Pass, @@ -40,10 +40,11 @@ add_pass_to_pipeline :: proc(renderer: ^Renderer, pass: ^Pass) -> bool { pipeline := &renderer.pipeline - if pipeline.amount_of_passes == PIPELINE_MAX_PASSES { - log.warnf("Failed to add scene-pass '%v' to renderer's pipeline; hit max capacity (%v).", pass.name, PIPELINE_MAX_PASSES) - return false - } + assert(pipeline.amount_of_passes < PIPELINE_MAX_PASSES) + // if pipeline.amount_of_passes == PIPELINE_MAX_PASSES { + // log.warnf("Failed to add scene-pass '%v' to renderer's pipeline; hit max capacity (%v).", pass.name, PIPELINE_MAX_PASSES) + // return false + // } pipeline.passes[pipeline.amount_of_passes] = pass pipeline.amount_of_passes += 1 diff --git a/renderer.odin b/renderer.odin index 6e01087..61f702e 100644 --- a/renderer.odin +++ b/renderer.odin @@ -12,7 +12,10 @@ 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, @@ -37,19 +40,44 @@ Polygon_Mode :: enum { Point, } +Resolution :: struct { + width, height: u16, +} + +Backbuffer :: struct { + resolution: Resolution, +} + Viewport :: struct { - x, y, width, height: u16, + x, y: u16, + res: Resolution, } -get_aspect_ratio :: proc(renderer: ^Renderer) -> f32 { +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 + } - viewport := &renderer.viewport - return f32(viewport.width) / f32(viewport.height) + return f32(res.width) / f32(res.height) } -create :: proc(surface_ptr: rawptr) -> (^Renderer, bool) { +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 { @@ -119,21 +147,37 @@ create :: proc(surface_ptr: rawptr) -> (^Renderer, bool) { 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) +set_backbuffer_resolution :: proc(renderer: ^Renderer, res: Resolution) { + assert(renderer != nil) + renderer.backbuffer.resolution = res +} - renderer.viewport = { - x = x, - y = y, - width = width, - height = height, - } +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) @@ -158,9 +202,9 @@ set_vsync :: proc(renderer: ^Renderer, on: bool) { } } -@(private) clear_screen :: proc(renderer: ^Renderer, clear_color: bool, clear_depth: bool) { +@(private) clear_screen :: proc(renderer: ^Renderer, clear_color, clear_depth, scissor: bool) { when RENDER_BACKEND_OPENGL { - opengl_clear_screen(renderer, clear_color, clear_depth) + opengl_clear_screen(renderer, clear_color, clear_depth, scissor) } } @@ -173,25 +217,51 @@ render_frame :: proc(renderer: ^Renderer, texture_to_present: ^Texture, clear_co camera_view_matrix, _ := get_camera_view_matrix(renderer.active_camera) camera_projection_matrix, _ := get_camera_projection_matrix(renderer, renderer.active_camera) - set_clear_color(renderer, clear_color) - clear_screen(renderer, true, true) - 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) + clear_screen(renderer, true, true, true) // Create a temporary Material. mat := Material { @@ -230,8 +300,6 @@ execute_post_processing_node :: proc(renderer: ^Renderer, node: ^Post_Processing defer bind_render_target(renderer, nil) apply_depth(renderer, false, false) - set_clear_color(renderer, RGBA_Color { 0, 0, 0, 0 }) - 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) @@ -252,13 +320,13 @@ execute_post_processing_node :: proc(renderer: ^Renderer, node: ^Post_Processing } 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) } - - // TODO: SS - Destroy 'default_cube_mesh' - // TODO: SS - Destroy 'default_quad_mesh' - // TODO: SS - Destroy 'default_sphere_mesh' assert(renderer != nil) free(renderer) diff --git a/renderer_backend_opengl_windows.odin b/renderer_backend_opengl_windows.odin index feb3c1c..5476f68 100644 --- a/renderer_backend_opengl_windows.odin +++ b/renderer_backend_opengl_windows.odin @@ -90,21 +90,23 @@ when RENDER_BACKEND_OPENGL { return true } - - opengl_viewport_changed :: proc(renderer: ^Renderer) { + @(private) opengl_apply_renderer_viewport :: proc(renderer: ^Renderer) { gl.Viewport( i32(renderer.viewport.x), i32(renderer.viewport.y), - i32(renderer.viewport.width), i32(renderer.viewport.height) + i32(renderer.viewport.res.width), i32(renderer.viewport.res.height) ) } + opengl_viewport_changed :: proc(renderer: ^Renderer) { + opengl_apply_renderer_viewport(renderer) + } + opengl_set_vsync :: proc(renderer: ^Renderer, on: bool) -> bool { if win.wglSwapIntervalEXT == nil { fmt.printfln("'wglSwapIntervalEXT' is nil.") return false } - // Kommer inte in hit. win.wglSwapIntervalEXT(on ? 1 : 0) return true @@ -121,7 +123,17 @@ when RENDER_BACKEND_OPENGL { gl.ClearColor(r, g, b, a) } - opengl_clear_screen :: proc(renderer: ^Renderer, clear_color: bool, clear_depth: bool) { + opengl_clear_screen :: proc(renderer: ^Renderer, clear_color, clear_depth, scissor: bool) { + if scissor { + gl.Enable(gl.SCISSOR_TEST) + gl.Scissor( + i32(renderer.viewport.x), + i32(renderer.viewport.y), + i32(renderer.viewport.res.width), + i32(renderer.viewport.res.height) + ) + } + flags := u32(0) if clear_color { flags |= gl.COLOR_BUFFER_BIT @@ -130,6 +142,10 @@ when RENDER_BACKEND_OPENGL { flags |= gl.DEPTH_BUFFER_BIT } gl.Clear(flags) + + if scissor { + gl.Disable(gl.SCISSOR_TEST) + } } opengl_swap_buffers :: proc(renderer: ^Renderer) { @@ -208,6 +224,18 @@ when RENDER_BACKEND_OPENGL { return m, true } + opengl_delete_mesh :: proc(renderer: ^Renderer, mesh: ^Mesh) { + if mesh == nil { return } + + gl.DeleteBuffers(1, &mesh.backend.vbo) + gl.DeleteBuffers(1, &mesh.backend.ebo) + gl.DeleteVertexArrays(1, &mesh.backend.vao) + + mesh.backend.vbo = 0 + mesh.backend.ebo = 0 + mesh.backend.vao = 0 + } + opengl_create_shader :: proc(renderer: ^Renderer, type: Shader_Type, path: string, data: []u8) -> (Shader_OpenGL, bool) { handle: u32 @@ -544,12 +572,7 @@ when RENDER_BACKEND_OPENGL { if rt == nil { gl.BindFramebuffer(gl.FRAMEBUFFER, 0) - gl.Viewport( - 0, - 0, - i32(renderer.viewport.width), - i32(renderer.viewport.height), - ) + opengl_apply_renderer_viewport(renderer) return }