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

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