Files
renderer/renderer.odin
2026-02-03 20:31:12 +01:00

480 lines
16 KiB
Odin

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,
vsync: bool,
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)
}
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
}
set_vsync(renderer, true)
{ // 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)
}
}
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="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
}
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)
}
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,
}
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_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)
defer bind_render_target(renderer, nil)
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)
defer apply_blend_mode(renderer, .None)
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)
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)
mat: Material
mat.shader_program = node.program
fs_path := node.program.fragment_shader != nil ? node.program.fragment_shader.path : "nil"
vs_path := node.program.vertex_shader != nil ? node.program.vertex_shader.path : "nil"
for u, i in node.uniforms {
switch &t in u {
case Uniform_Texture: {
if mat.texture_count > MATERIAL_MAX_TEXTURES {
continue
}
mat.textures[mat.texture_count] = t.value
mat.texture_count += 1
if !set_shader_uniform(node.program, t) {
fmt.printfln("Failed to set uniform-texture %v in program (vs: '%s', fs: '%s').", t.index, vs_path, fs_path)
}
}
case Uniform_Float: {
if !set_shader_uniform(node.program, t) {
fmt.printfln("Failed to set uniform-float '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path)
}
}
case Uniform_Matrix4f32: {
if !set_shader_uniform(node.program, t) {
fmt.printfln("Failed to set uniform-matrix4f32 '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path)
}
}
case Uniform_Vector3: {
if !set_shader_uniform(node.program, t) {
fmt.printfln("Failed to set uniform-vector3 '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path)
}
}
case Uniform_Color: {
if !set_shader_uniform(node.program, t) {
fmt.printfln("Failed to set uniform-color '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path)
}
}
}
}
activate_fullscreen_material(renderer, &mat)
draw_mesh(&renderer.fullscreen_mesh)
}
destroy :: proc(renderer: ^Renderer) {
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)
fs_path := p.fragment_shader != nil ? p.fragment_shader.path : "nil"
vs_path := p.vertex_shader != nil ? p.vertex_shader.path : "nil"
for u in material.uniforms {
switch &t in u {
case Uniform_Texture: {
if !set_shader_uniform(p, t) {
fmt.printfln("Failed to set uniform-texture %v in program (vs: '%s', fs: '%s').", t.index, vs_path, fs_path)
}
}
case Uniform_Float: {
if !set_shader_uniform(p, t) {
fmt.printfln("Failed to set uniform-float '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path)
}
}
case Uniform_Matrix4f32: {
if !set_shader_uniform(p, t) {
fmt.printfln("Failed to set uniform-matrix4f32 '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path)
}
}
case Uniform_Vector3: {
if !set_shader_uniform(p, t) {
fmt.printfln("Failed to set uniform-vector3 '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path)
}
}
case Uniform_Color: {
if !set_shader_uniform(p, t) {
fmt.printfln("Failed to set uniform-color '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path)
}
}
}
}
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)
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()
}
}