Letterbox, maintain render-resolution's aspect ratio

This commit is contained in:
2026-02-05 17:00:21 +01:00
parent 3b32097c56
commit cb19295575
6 changed files with 153 additions and 47 deletions

View File

@@ -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,

View File

@@ -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)
@@ -234,3 +236,12 @@ 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)
}
}

View File

@@ -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, should_scissor)
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)

View File

@@ -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

View File

@@ -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,
}
Viewport :: struct {
x, y, width, height: u16,
Resolution :: struct {
width, height: u16,
}
get_aspect_ratio :: proc(renderer: ^Renderer) -> f32 {
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)
viewport := &renderer.viewport
return f32(viewport.width) / f32(viewport.height)
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(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,9 +217,6 @@ 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)
}
@@ -189,9 +230,38 @@ render_frame :: proc(renderer: ^Renderer, texture_to_present: ^Texture, clear_co
// 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,14 +320,14 @@ 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)
}

View File

@@ -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
}