398 lines
12 KiB
Odin
398 lines
12 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,
|
|
|
|
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,
|
|
}
|
|
|
|
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 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_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) 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: 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)
|
|
}
|
|
|
|
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)
|
|
|
|
// 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_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)
|
|
|
|
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) {
|
|
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)
|
|
}
|
|
|
|
@(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)
|
|
}
|
|
} |