Compare commits

..

2 Commits

Author SHA1 Message Date
bd4b81d434 Work on passes/pipeline, render targets 2026-01-26 20:45:29 +01:00
6b2d76bd8a Perspective + orthographic cameras 2026-01-26 20:43:45 +01:00
8 changed files with 877 additions and 177 deletions

View File

@@ -6,27 +6,95 @@ import "core:math/linalg"
Camera :: struct { Camera :: struct {
position: [3]f32, position: [3]f32,
forward, up, right: [3]f32, forward, up, right: [3]f32,
fov_degrees: f32,
near, far: f32, near, far: f32,
yaw, pitch, roll: f32,
type: Camera_Type,
} }
create_camera :: proc(renderer: ^Renderer, position: [3]f32, forward, up: [3]f32, fov_degrees, near, far: f32) -> Camera { Camera_Type :: union {
Camera_Perspective,
Camera_Orthographic,
}
Camera_Perspective :: struct {
fov_degrees: f32,
}
Camera_Orthographic :: struct {
height: f32,
}
@(private) create_camera_base :: proc(
renderer: ^Renderer,
position: [3]f32,
forward, up: [3]f32,
near, far: f32,
type: Camera_Type,
yaw: f32 = 0, pitch: f32 = 0, roll: f32 = 0
) -> Camera
{
c: Camera c: Camera
c.position = position c.position = position
c.forward = forward c.forward = forward
c.up = up c.up = up
c.fov_degrees = fov_degrees
c.near = near c.near = near
c.far = far c.far = far
c.type = type
c.yaw = yaw
c.pitch = pitch
c.roll = roll
assert(c.forward != {}) assert(c.forward != {})
assert(c.up != {}) assert(c.up != {})
assert(c.near > 0)
assert(c.far > 0)
return c
}
create_camera_perspective :: proc(
renderer: ^Renderer,
position: [3]f32,
forward, up: [3]f32,
near, far: f32,
fov_degrees: f32,
yaw: f32 = 0, pitch: f32 = 0, roll: f32 = 0
) -> Camera
{
assert(fov_degrees > 0) assert(fov_degrees > 0)
assert(near > 0) assert(near > 0)
assert(far > 0) assert(far > 0)
return c return create_camera_base(
renderer, position, forward, up,
near, far,
Camera_Perspective {
fov_degrees = fov_degrees,
},
yaw, pitch, roll
)
}
create_camera_orthographic :: proc(
renderer: ^Renderer,
position: [3]f32,
forward, up: [3]f32,
near, far: f32,
height: f32,
yaw: f32 = 0, pitch: f32 = 0, roll: f32 = 0
) -> Camera
{
assert(height > 0)
return create_camera_base(
renderer, position, forward, up,
near, far,
Camera_Orthographic {
height = height,
},
yaw, pitch, roll
)
} }
get_camera_view_matrix :: proc(camera: ^Camera) -> (linalg.Matrix4f32, bool) { get_camera_view_matrix :: proc(camera: ^Camera) -> (linalg.Matrix4f32, bool) {
@@ -48,23 +116,50 @@ get_camera_projection_matrix :: proc(renderer: ^Renderer, camera: ^Camera) -> (l
} }
projection_matrix := linalg.MATRIX4F32_IDENTITY projection_matrix := linalg.MATRIX4F32_IDENTITY
switch t in camera.type {
case Camera_Perspective: {
projection_matrix *= linalg.matrix4_perspective( projection_matrix *= linalg.matrix4_perspective(
linalg.to_radians(f32(camera.fov_degrees)), linalg.to_radians(f32(t.fov_degrees)),
get_aspect_ratio(renderer), get_aspect_ratio(renderer),
camera.near, camera.near,
camera.far, camera.far,
flip_z_axis = true, flip_z_axis = true,
) )
}
case Camera_Orthographic: {
half_h := t.height / 2
half_w := half_h * get_aspect_ratio(renderer)
projection_matrix *= linalg.matrix_ortho3d(
-half_w, half_w,
-half_h, half_h,
camera.near,
camera.far,
flip_z_axis = true,
)
}
}
return projection_matrix, true return projection_matrix, true
} }
rotate_camera :: proc(camera: ^Camera, yaw, pitch, roll: f32) { rotate_camera :: proc(camera: ^Camera, yaw, pitch, roll: f32) {
pitch := clamp(pitch, -89.0, 89.0) new_yaw := camera.yaw + yaw
new_pitch := camera.pitch + pitch
new_roll := camera.roll + roll
pitch_rad := linalg.to_radians(pitch) set_camera_rotation(camera, new_yaw, new_pitch, new_roll)
yaw_rad := linalg.to_radians(yaw) }
roll_rad := linalg.to_radians(roll)
set_camera_rotation :: proc(camera: ^Camera, yaw, pitch, roll: f32) {
camera.yaw = yaw
camera.pitch = pitch
camera.roll = roll
pitch_rad := linalg.to_radians(camera.pitch)
yaw_rad := linalg.to_radians(camera.yaw)
roll_rad := linalg.to_radians(camera.roll)
camera.forward = [3]f32{ camera.forward = [3]f32{
math.cos(pitch_rad) * math.cos(yaw_rad), math.cos(pitch_rad) * math.cos(yaw_rad),
@@ -73,9 +168,9 @@ rotate_camera :: proc(camera: ^Camera, yaw, pitch, roll: f32) {
} }
WORLD_UP :: [3]f32{0, 1, 0} WORLD_UP :: [3]f32{0, 1, 0}
camera.right = linalg.normalize(linalg.cross(camera.forward, WORLD_UP)) camera.right = linalg.normalize(linalg.cross(camera.forward, WORLD_UP))
camera.up = linalg.normalize(linalg.cross(camera.right, camera.forward)) camera.up = linalg.normalize(linalg.cross(camera.right, camera.forward))
// TODO: SS - Support 'roll'.
} }
set_active_camera :: proc(renderer: ^Renderer, camera: ^Camera) { set_active_camera :: proc(renderer: ^Renderer, camera: ^Camera) {
@@ -83,5 +178,6 @@ set_active_camera :: proc(renderer: ^Renderer, camera: ^Camera) {
} }
get_active_camera :: proc(renderer: ^Renderer) -> ^Camera { get_active_camera :: proc(renderer: ^Renderer) -> ^Camera {
assert(renderer != nil)
return renderer.active_camera return renderer.active_camera
} }

View File

@@ -1,7 +1,16 @@
package renderer package renderer
Material :: struct { Material :: struct {
shader_program: Shader_Program, // 'Name'?
texture: Texture, // Diffuse, normal etc later. shader_program: ^Shader_Program,
texture: ^Texture, // Diffuse, normal etc later.
// uniforms, textures, etc. // uniforms, textures, etc.
} }
create_material :: proc(program: ^Shader_Program, texture0: ^Texture) -> (Material, bool) {
m: Material
m.shader_program = program
m.texture = texture0
return m, true
}

View File

@@ -8,8 +8,13 @@ Mesh :: struct {
Mesh_Handle :: distinct u32 Mesh_Handle :: distinct u32
Buffer_Handle :: distinct u32 Buffer_Handle :: distinct u32
Mesh_Layout :: struct {
name: string,
amount: u8,
type_size: u32,
}
create_mesh :: proc(renderer: ^Renderer, vertices: []f32, indices: []u32) -> (Mesh, bool) { // TODO: SS - Should probably return a Mesh_Handle. create_mesh :: proc(renderer: ^Renderer, layout: []Mesh_Layout, vertices: []f32, indices: []u32) -> (Mesh, bool) { // TODO: SS - Should probably return a Mesh_Handle.
mesh: Mesh mesh: Mesh
if len(vertices) == 0 { if len(vertices) == 0 {
@@ -23,7 +28,7 @@ create_mesh :: proc(renderer: ^Renderer, vertices: []f32, indices: []u32) -> (Me
m.amount_of_indices = u32(len(indices)) m.amount_of_indices = u32(len(indices))
when RENDER_BACKEND_OPENGL { when RENDER_BACKEND_OPENGL {
opengl_mesh, ok := opengl_create_mesh(renderer, vertices, indices) opengl_mesh, ok := opengl_create_mesh(renderer, layout, vertices, indices)
if !ok { if !ok {
return {}, false return {}, false
} }

146
pass.odin
View File

@@ -2,35 +2,155 @@ package renderer
import "core:fmt" import "core:fmt"
MAX_DRAW_COMMANDS_PER_PASS :: 1024 MAX_DRAW_COMMANDS_CAPACITY :: 4096
Pass :: struct { PASS_MAX_INPUT_TEXTURES :: 8
Scene_Pass :: struct {
name: string, name: string,
clear_color: RGB_Color,
should_test_depth: bool, input_textures: [PASS_MAX_INPUT_TEXTURES]^Texture, // TODO: SS - Make this a map?
should_clear_depth: bool, input_texture_count: u8,
draw_commands: [MAX_DRAW_COMMANDS_PER_PASS]Draw_Command, output_rt: ^Render_Target,
draw_command_count: u32,
draw_commands: [dynamic]Draw_Command, // Capacity is 'MAX_DRAW_COMMANDS_CAPACITY'.
clear_color: Maybe(RGB_Color),
blend_mode: Blend_Mode,
sort_mode: Sort_Mode,
}
Post_Processing_Pass :: struct {
name: string,
input_texture: ^Texture, // TODO: SS - Make this an array of texture-pointers, or maybe a map.
input_depth_texture: ^Texture,
output_rt: ^Render_Target,
shader_program: Shader_Program,
} }
Draw_Command :: struct { Draw_Command :: struct {
renderer: ^Renderer, // Needed for sorting.
mesh: Mesh, mesh: Mesh,
material: Material, material: Material,
position: [3]f32, position: [3]f32,
rotation: [3]f32, rotation: [3]f32,
scale: [3]f32, scale: [3]f32,
// TODO: SS - Add rotation.
} }
add_command_to_pass :: proc(pass: ^Pass, command: Draw_Command) -> bool { Blend_Mode :: enum {
if pass.draw_command_count >= len(pass.draw_commands) { None,
return false Alpha,
Additive,
Multiply,
}
Blend_Factor :: enum {
Zero,
One,
Src_Color,
One_Minus_Src_Color,
Dst_Color,
One_Minus_Dst_Color,
Src_Alpha,
One_Minus_Src_Alpha,
Dst_Alpha,
One_Minus_Dst_Alpha,
// ..
}
Blend_Factors :: struct {
source, destination: Blend_Factor
}
@(private) BLEND_FACTOR_TABLE :: [Blend_Mode]Blend_Factors {
.None = { .One, .Zero },
.Alpha = { .Src_Alpha, .One_Minus_Src_Alpha },
.Additive = { .Src_Alpha, .One },
.Multiply = { .Dst_Color, .Zero },
}
Sort_Mode :: enum {
None, // Draws the commands in the order they're placed in the 'draw_commmands' array.
Back_To_Front, // Sorts the commands in the 'draw_commmands' array (from back to front) before drawing them.
Front_To_Back, // Sorts the commands in the 'draw_commmands' array (from front to back) before drawing them.
}
create_scene_pass :: proc(
name: string,
input_textures: []^Texture,
output_rt: ^Render_Target,
clear_color: Maybe(RGB_Color),
blend_mode: Blend_Mode,
sort_mode: Sort_Mode,
) -> Scene_Pass
{
// if input_depth_texture == nil {
// if test_depth || clear_depth || write_depth {
// assert(false)
// }
// }
p := Scene_Pass {
name = name,
output_rt = output_rt,
draw_commands = make([dynamic]Draw_Command, 0, MAX_DRAW_COMMANDS_CAPACITY),
clear_color = clear_color,
blend_mode = blend_mode,
sort_mode = sort_mode,
} }
pass.draw_commands[pass.draw_command_count] = command for texture, i in input_textures {
pass.draw_command_count += 1 if p.input_texture_count >= PASS_MAX_INPUT_TEXTURES {
fmt.printfln("Hit max capacity of textures per pass.")
break
}
p.input_textures[p.input_texture_count] = texture
p.input_texture_count += 1
}
return p
}
create_post_processing_pass :: proc(
name: string,
input_texture, input_depth_texture: ^Texture,
output_rt: ^Render_Target,
shader_program: Shader_Program,
) -> Post_Processing_Pass
{
return {
name = name,
input_texture = input_texture,
input_depth_texture = input_depth_texture,
output_rt = output_rt,
shader_program = shader_program,
}
}
delete_pass :: proc {
delete_scene_pass,
}
@(private) delete_scene_pass :: proc(pass: ^Scene_Pass) {
delete(pass.draw_commands)
}
add_command_to_pass :: proc(renderer: ^Renderer, pass: ^Scene_Pass, command: Draw_Command) -> bool {
assert(renderer != nil)
assert(pass != nil)
cmd := command
cmd.renderer = renderer
n, err := append(&pass.draw_commands, cmd)
assert(err == .None)
return true return true
} }

View File

@@ -3,45 +3,92 @@ package renderer
import "core:mem" import "core:mem"
import "core:log" import "core:log"
MAX_PASSES_IN_PIPELINE :: 8 PIPELINE_MAX_SCENE_PASSES :: 8
PIPELINE_MAX_POST_PROCESSING_PASSES :: 8
Pipeline :: struct { Pipeline :: struct {
passes: [MAX_PASSES_IN_PIPELINE]^Pass, scene_passes: [PIPELINE_MAX_SCENE_PASSES]^Scene_Pass,
amount_of_passes: u8, amount_of_scene_passes: u8,
post_processing_passes: [PIPELINE_MAX_POST_PROCESSING_PASSES]^Post_Processing_Pass,
amount_of_post_processing_passes: u8,
fullscreen_material: ^Material,
fullscreen_mesh: ^Mesh,
} }
clear_pipeline :: proc(renderer: ^Renderer) { clear_pipeline :: proc(renderer: ^Renderer) {
assert(renderer != nil) assert(renderer != nil)
pipeline := &renderer.pipeline p := &renderer.pipeline
pipeline.amount_of_passes = 0; p.amount_of_scene_passes = 0;
mem.set(&pipeline.passes[0], 0, MAX_PASSES_IN_PIPELINE * size_of(^Pass)) mem.set(&p.scene_passes[0], 0, PIPELINE_MAX_SCENE_PASSES * size_of(^Scene_Pass))
p.amount_of_post_processing_passes = 0;
mem.set(&p.post_processing_passes[0], 0, PIPELINE_MAX_POST_PROCESSING_PASSES * size_of(^Post_Processing_Pass))
} }
set_pipeline :: proc(renderer: ^Renderer, passes: []^Pass) { set_pipeline :: proc(
clear_pipeline(renderer) renderer: ^Renderer,
for p in passes { fullscreen_material: ^Material, fullscreen_mesh: ^Mesh,
if !add_pass_to_pipeline(renderer, p) { scene_passes: []^Scene_Pass,
return post_processing_passes: []^Post_Processing_Pass,
} ){
}
}
@(private="file") add_pass_to_pipeline :: proc(renderer: ^Renderer, pass: ^Pass) -> bool {
assert(renderer != nil) assert(renderer != nil)
assert(pass != nil)
clear_pipeline(renderer)
renderer.pipeline.fullscreen_material = fullscreen_material
renderer.pipeline.fullscreen_mesh = fullscreen_mesh
for p in scene_passes {
if !add_pass_to_pipeline(renderer, p) {
break
}
}
for p in post_processing_passes {
if !add_pass_to_pipeline(renderer, p) {
break
}
}
}
add_pass_to_pipeline :: proc {
add_scene_pass_to_pipeline,
add_post_processing_pass_to_pipeline,
}
@(private="file") add_scene_pass_to_pipeline :: proc(renderer: ^Renderer, scene_pass: ^Scene_Pass) -> bool {
assert(renderer != nil)
assert(scene_pass != nil)
pipeline := &renderer.pipeline pipeline := &renderer.pipeline
if pipeline.amount_of_passes == MAX_PASSES_IN_PIPELINE { if pipeline.amount_of_scene_passes == PIPELINE_MAX_SCENE_PASSES {
log.warnf("Failed to add pass '%v' to renderer's pipeline; hit max capacity (%v).", pass.name, MAX_PASSES_IN_PIPELINE) log.warnf("Failed to add scene-pass '%v' to renderer's pipeline; hit max capacity (%v).", scene_pass.name, PIPELINE_MAX_SCENE_PASSES)
return false return false
} }
pipeline.passes[pipeline.amount_of_passes] = pass pipeline.scene_passes[pipeline.amount_of_scene_passes] = scene_pass
pipeline.amount_of_passes += 1 pipeline.amount_of_scene_passes += 1
// log.infof("Successfully added pass '%v' to pipeline.", pass.name) return true
}
@(private="file") add_post_processing_pass_to_pipeline :: proc(renderer: ^Renderer, post_processing_pass: ^Post_Processing_Pass) -> bool {
assert(renderer != nil)
assert(post_processing_pass != nil)
pipeline := &renderer.pipeline
if pipeline.amount_of_post_processing_passes == PIPELINE_MAX_POST_PROCESSING_PASSES {
log.warnf("Failed to add post-processing pass '%v' to renderer's pipeline; hit max capacity (%v).", post_processing_pass.name, PIPELINE_MAX_POST_PROCESSING_PASSES)
return false
}
pipeline.post_processing_passes[pipeline.amount_of_post_processing_passes] = post_processing_pass
pipeline.amount_of_post_processing_passes += 1
return true return true
} }

View File

@@ -1,5 +1,7 @@
package renderer package renderer
import "core:slice"
import "core:sort"
import "core:math/linalg" import "core:math/linalg"
import "core:fmt" import "core:fmt"
import "core:log" import "core:log"
@@ -75,9 +77,9 @@ set_viewport :: proc(renderer: ^Renderer, x, y, width, height: u16) {
} }
} }
@(private="file") clear_screen :: proc(renderer: ^Renderer, clear_depth: bool) { @(private="file") clear_screen :: proc(renderer: ^Renderer, clear_color: bool, clear_depth: bool) {
when RENDER_BACKEND_OPENGL { when RENDER_BACKEND_OPENGL {
opengl_clear_screen(renderer, clear_depth) opengl_clear_screen(renderer, clear_color, clear_depth)
} }
} }
@@ -87,54 +89,103 @@ render_frame :: proc(renderer: ^Renderer) {
return return
} }
view_matrix, view_matrix_ok := get_camera_view_matrix(renderer.active_camera) view_matrix, _ := get_camera_view_matrix(renderer.active_camera)
assert(view_matrix_ok) projection_matrix, _ := get_camera_projection_matrix(renderer, renderer.active_camera)
projection_matrix, projection_matrix_ok := get_camera_projection_matrix(renderer, renderer.active_camera)
assert(projection_matrix_ok)
pipeline := &renderer.pipeline pipeline := &renderer.pipeline
for i in 0 ..< pipeline.amount_of_passes {
pass := pipeline.passes[i]
set_clear_color(renderer, pass.clear_color) for i in 0 ..< pipeline.amount_of_scene_passes {
clear_screen(renderer, pass.should_clear_depth) execute_scene_pass(renderer, pipeline.scene_passes[i], view_matrix, projection_matrix)
enable_depth_testing(renderer, pass.should_test_depth)
for i in 0..<pass.draw_command_count {
command := &pass.draw_commands[i]
model_matrix := linalg.identity(linalg.Matrix4x4f32)
// Translate.
translation := linalg.matrix4_translate(command.position)
// Rotate.
rot_x := linalg.matrix4_rotate(linalg.to_radians(command.rotation.x), [3]f32 { 1, 0, 0 })
rot_y := linalg.matrix4_rotate(linalg.to_radians(command.rotation.y), [3]f32 { 0, 1, 0 })
rot_z := linalg.matrix4_rotate(linalg.to_radians(command.rotation.z), [3]f32 { 0, 0, 1 })
rotation := rot_z * rot_y * rot_x
// Scale.
scale := linalg.matrix4_scale(command.scale)
model_matrix *= translation * rotation * scale
activate_material(&command.material, model_matrix, view_matrix, projection_matrix)
draw_mesh(&command.mesh)
} }
// TODO: SS - "Deactivate" the pass. for i in 0 ..< pipeline.amount_of_post_processing_passes {
execute_post_processing_pass(renderer, pipeline.post_processing_passes[i], view_matrix, projection_matrix)
}
// Clear the pass' draw-commands. bind_render_target(renderer, nil) // Det görs ju här..
pass.draw_command_count = 0
if pipeline.fullscreen_material != nil {
if pipeline.fullscreen_material.texture != nil {
activate_fullscreen_material(renderer)
if pipeline.fullscreen_mesh != nil {
draw_mesh(pipeline.fullscreen_mesh)
}
else {
log.warn("Renderer is missing a fullscreen-mesh.")
}
deactivate_fullscreen_material(renderer)
}
else {
log.warn("Fullscreen-material does not have a texture.")
}
}
else {
log.warn("Renderer is missing a fullscreen-material.")
} }
when RENDER_BACKEND_OPENGL { when RENDER_BACKEND_OPENGL {
opengl_swap_buffers(renderer) opengl_swap_buffers(renderer)
} }
}
clear_screen(renderer, true) execute_scene_pass :: proc(renderer: ^Renderer, pass: ^Scene_Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) {
// fmt.printfln("Rendering pass '%v' to output_rt %v.", pass.name, pass.output_rt)
// TODO: SS - Use 'pass.input_textures'
assert(pass.output_rt != nil)
bind_render_target(renderer, pass.output_rt)
should_write_depth := pass.output_rt.depth_texture != nil
should_test_depth := should_write_depth
should_clear_depth := should_write_depth
cc, has_clear_color := pass.clear_color.?
if has_clear_color {
set_clear_color(renderer, cc)
}
should_clear_color := has_clear_color && pass.output_rt.color_texture != nil
clear_screen(renderer, should_clear_color, should_clear_depth)
apply_depth(renderer, should_test_depth, should_write_depth)
apply_blend_mode(renderer, pass.blend_mode)
sort_draw_commands(renderer, pass)
for &dc in &pass.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)
}
// TODO: SS - "Deactivate" the pass.
// Clear the pass' draw-commands.
clear(&pass.draw_commands)
}
execute_post_processing_pass :: proc(renderer: ^Renderer, pass: ^Post_Processing_Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) {
fmt.printfln("TODO: SS - Execute post-processing pass '%v'.", pass.name)
} }
destroy :: proc(renderer: ^Renderer) { destroy :: proc(renderer: ^Renderer) {
@@ -154,6 +205,21 @@ destroy :: proc(renderer: ^Renderer) {
} }
} }
@(private) activate_fullscreen_material :: proc(renderer: ^Renderer) {
assert(renderer != nil)
mat := renderer.pipeline.fullscreen_material
when RENDER_BACKEND_OPENGL {
opengl_activate_fullscreen_material(mat)
}
}
@(private) deactivate_fullscreen_material :: proc(renderer: ^Renderer) {
assert(renderer != nil)
when RENDER_BACKEND_OPENGL {
opengl_deactivate_fullscreen_material()
}
}
@(private) draw_mesh :: proc(mesh: ^Mesh) { @(private) draw_mesh :: proc(mesh: ^Mesh) {
assert(mesh != nil) assert(mesh != nil)
@@ -162,8 +228,89 @@ destroy :: proc(renderer: ^Renderer) {
} }
} }
@(private) enable_depth_testing :: proc(renderer: ^Renderer, enable: bool) { @(private) apply_depth :: proc(renderer: ^Renderer, test_depth, write_depth: bool) {
when RENDER_BACKEND_OPENGL { when RENDER_BACKEND_OPENGL {
opengl_enable_depth_testing(renderer, true) 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_framebuffer :: proc(renderer: ^Renderer, rt: ^Render_Target) -> bool {
// when RENDER_BACKEND_OPENGL {
// return opengl_bind_framebuffer(renderer, rt)
// }
return false
}
@(private) bind_depth_texture :: proc(renderer: ^Renderer, rt: ^Render_Target) -> bool {
// when RENDER_BACKEND_OPENGL {
// return opengl_bind_depth_texture(renderer, rt)
// }
return false
}
@(private) bind_render_target :: proc(renderer: ^Renderer, rt: ^Render_Target) {
when RENDER_BACKEND_OPENGL {
opengl_bind_render_target(renderer, rt)
} }
} }

View File

@@ -101,14 +101,15 @@ when RENDER_BACKEND_OPENGL {
) )
} }
opengl_clear_screen :: proc(renderer: ^Renderer, clear_depth: bool) { opengl_clear_screen :: proc(renderer: ^Renderer, clear_color: bool, clear_depth: bool) {
mask := u32(gl.COLOR_BUFFER_BIT) flags := u32(0)
if clear_depth { if clear_color {
gl.ClearDepth(1.0) flags |= gl.COLOR_BUFFER_BIT
mask |= gl.DEPTH_BUFFER_BIT
} }
if clear_depth {
gl.Clear(mask) flags |= gl.DEPTH_BUFFER_BIT
}
gl.Clear(flags)
} }
opengl_swap_buffers :: proc(renderer: ^Renderer) { opengl_swap_buffers :: proc(renderer: ^Renderer) {
@@ -127,6 +128,7 @@ when RENDER_BACKEND_OPENGL {
Shader_Backend :: Shader_OpenGL Shader_Backend :: Shader_OpenGL
Shader_Program_Backend :: Shader_Program_OpenGL Shader_Program_Backend :: Shader_Program_OpenGL
Texture_Backend :: Texture_OpenGL Texture_Backend :: Texture_OpenGL
Render_Target_Backend :: Render_Target_OpenGL
Mesh_OpenGL :: struct { Mesh_OpenGL :: struct {
vbo, vao, ebo: u32, vbo, vao, ebo: u32,
@@ -144,7 +146,11 @@ when RENDER_BACKEND_OPENGL {
handle: u32, handle: u32,
} }
opengl_create_mesh :: proc(renderer: ^Renderer, vertices: []f32, indices: []u32) -> (Mesh_OpenGL, bool) { Render_Target_OpenGL :: struct {
handle: u32,
}
opengl_create_mesh :: proc(renderer: ^Renderer, layout: []Mesh_Layout, vertices: []f32, indices: []u32) -> (Mesh_OpenGL, bool) {
// fmt.printfln("OPENGL: Creating mesh from vertices: %v.", vertices) // fmt.printfln("OPENGL: Creating mesh from vertices: %v.", vertices)
m: Mesh_OpenGL m: Mesh_OpenGL
@@ -161,14 +167,20 @@ when RENDER_BACKEND_OPENGL {
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, m.ebo) gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, m.ebo)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices) * size_of(u32), raw_data(indices), gl.STATIC_DRAW) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices) * size_of(u32), raw_data(indices), gl.STATIC_DRAW)
gl.VertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 8 * size_of(f32), uintptr(0)) layout_total_stride := u32(0)
gl.EnableVertexAttribArray(0) for l in layout {
layout_total_stride += u32(l.amount) * l.type_size;
}
gl.VertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 8 * size_of(f32), uintptr(3 * size_of(f32))) offset := u32(0)
gl.EnableVertexAttribArray(1) for l, i in layout {
// NOTE: SS - Hardcoded to 'gl.FLOAT'
gl.VertexAttribPointer(u32(i), i32(l.amount), gl.FLOAT, gl.FALSE, i32(layout_total_stride), uintptr(offset))
gl.EnableVertexAttribArray(u32(i))
offset += u32(l.amount) * l.type_size
}
gl.VertexAttribPointer(2, 2, gl.FLOAT, gl.FALSE, 8 * size_of(f32), uintptr(6 * size_of(f32)))
gl.EnableVertexAttribArray(2)
} }
gl.BindVertexArray(0) gl.BindVertexArray(0)
gl.BindBuffer(gl.ARRAY_BUFFER, 0) gl.BindBuffer(gl.ARRAY_BUFFER, 0)
@@ -260,72 +272,153 @@ when RENDER_BACKEND_OPENGL {
gl.UniformMatrix4fv(projection_matrix_loc, 1, false, &projection_matrix_as_f32_array[0]) gl.UniformMatrix4fv(projection_matrix_loc, 1, false, &projection_matrix_as_f32_array[0])
} }
opengl_activate_fullscreen_material :: proc(material: ^Material) {
assert(material != nil)
gl.UseProgram(material.shader_program.backend.handle)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, material.texture.backend.handle)
loc := gl.GetUniformLocation(material.shader_program.backend.handle, "texture0") // eller "uTex"
if loc != -1 {
gl.Uniform1i(loc, 0)
}
gl.Disable(gl.DEPTH_TEST)
gl.DepthMask(gl.FALSE)
}
opengl_deactivate_fullscreen_material :: proc() {
gl.DepthMask(gl.TRUE)
gl.Enable(gl.DEPTH_TEST)
}
opengl_draw_mesh :: proc(mesh: ^Mesh) { opengl_draw_mesh :: proc(mesh: ^Mesh) {
gl.BindVertexArray(mesh.backend.vao) gl.BindVertexArray(mesh.backend.vao)
assert(mesh.amount_of_indices < u32(max(i32))) assert(mesh.amount_of_indices < u32(max(i32)))
gl.DrawElements(gl.TRIANGLES, i32(mesh.amount_of_indices), gl.UNSIGNED_INT, nil) gl.DrawElements(gl.TRIANGLES, i32(mesh.amount_of_indices), gl.UNSIGNED_INT, nil)
} }
opengl_texture_format_from_texture :: proc(t: ^Texture) -> (u32, u32, u32) {
internal, format, type: u32
switch t.format {
case .R8: {
internal = gl.R8
format = gl.RED
type = gl.UNSIGNED_BYTE
}
case .R16F: {
internal = gl.R16F
format = gl.RED
type = gl.HALF_FLOAT
}
case .R32F: {
internal = gl.R32F
format = gl.RED
type = gl.FLOAT
}
case .RGBA8: {
internal = gl.RGBA8
if t.channels == 3 {
format = gl.RGB
} else {
format = gl.RGBA
}
type = gl.UNSIGNED_BYTE
}
case .RGBA16F: {
internal = gl.RGBA16F
format = gl.RGBA
type = gl.HALF_FLOAT
}
case .R11G11B10F: {
internal = gl.R11F_G11F_B10F
format = gl.RGB
type = gl.UNSIGNED_INT_10F_11F_11F_REV
}
case .Depth16: {
internal = gl.DEPTH_COMPONENT16
format = gl.DEPTH_COMPONENT
type = gl.UNSIGNED_SHORT
}
case .Depth24: {
internal = gl.DEPTH_COMPONENT24
format = gl.DEPTH_COMPONENT
type = gl.UNSIGNED_INT
}
case .Depth32F: {
internal = gl.DEPTH_COMPONENT32F
format = gl.DEPTH_COMPONENT
type = gl.FLOAT
}
case .Depth24_Stencil8: {
internal = gl.DEPTH24_STENCIL8
format = gl.DEPTH_STENCIL
type = gl.UNSIGNED_INT_24_8
}
case .Depth32F_Stencil8: {
internal = gl.DEPTH32F_STENCIL8
format = gl.DEPTH_STENCIL
type = gl.FLOAT_32_UNSIGNED_INT_24_8_REV
}
}
return internal, format, type
}
opengl_load_texture :: proc(renderer: ^Renderer, t: ^Texture, pixels: rawptr) -> bool { opengl_load_texture :: proc(renderer: ^Renderer, t: ^Texture, pixels: rawptr) -> bool {
handle: u32 handle: u32
gl.GenTextures(1, &handle) gl.GenTextures(1, &handle)
gl.BindTexture(gl.TEXTURE_2D, handle) gl.BindTexture(gl.TEXTURE_2D, handle)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT) switch t.filter_mode {
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT) case .Nearest:
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
case .Linear:
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
case .Nearest_MipMap:
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
case .Linear_MipMap:
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
type := gl.UNSIGNED_BYTE
switch t.depth {
case 8: type = gl.UNSIGNED_BYTE
case 16: type = gl.UNSIGNED_SHORT
case 32: type = gl.UNSIGNED_INT
} }
format := gl.RGBA switch t.wrap_mode {
switch t.channels { case .Clamp:
case 1: format = gl.RED gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP)
case 2: format = gl.RG gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP)
case 3: format = gl.RGB case .Repeat:
case 4: format = gl.RGBA gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
case .Mirror:
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT)
} }
internal := gl.RGBA8 internal_format, format, type := opengl_texture_format_from_texture(t)
if t.depth == 8 {
switch t.channels {
case 1: internal = gl.R8
case 2: internal = gl.RG8
case 3: internal = gl.RGB8
case 4: internal = gl.RGBA8
}
} else if t.depth == 16 {
switch t.channels {
case 1: internal = gl.R16
case 2: internal = gl.RG16
case 3: internal = gl.RGB16
case 4: internal = gl.RGBA16
}
}
// bytes.reverse(bytes.buffer_to_bytes(img.pixels))
gl.TexImage2D( gl.TexImage2D(
target = gl.TEXTURE_2D, gl.TEXTURE_2D,
level = 0, 0,
internalformat = i32(internal), i32(internal_format),
width = i32(t.width), i32(t.width),
height = i32(t.height), i32(t.height),
border = 0, 0,
format = u32(format), u32(format),
type = u32(type), u32(type),
pixels = pixels, pixels,
// pixels = &pixels[0]
// pixels = &bytes.reverse(bytes.buffer_to_bytes(&img.pixels))[0]
) )
if !t.is_depth {
gl.GenerateMipmap(gl.TEXTURE_2D) gl.GenerateMipmap(gl.TEXTURE_2D)
}
t.backend = Texture_OpenGL { handle = handle } t.backend = Texture_OpenGL { handle = handle }
return true return true
@@ -335,12 +428,105 @@ when RENDER_BACKEND_OPENGL {
gl.DeleteTextures(1, &t.backend.handle) gl.DeleteTextures(1, &t.backend.handle)
} }
opengl_enable_depth_testing :: proc(renderer: ^Renderer, enable: bool) { opengl_apply_depth :: proc(renderer: ^Renderer, test_depth, write_depth: bool) {
if enable { if test_depth {
gl.Enable(gl.DEPTH_TEST) gl.Enable(gl.DEPTH_TEST)
} }
else { else {
gl.Disable(gl.DEPTH_TEST) gl.Disable(gl.DEPTH_TEST)
} }
gl.DepthMask(write_depth)
} }
@(private="file") OPENGL_BLEND_FACTOR_U32_TABLE :: [Blend_Factor]u32 {
.Zero = gl.ZERO,
.One = gl.ONE,
// Color.
.Src_Color = gl.SRC_COLOR,
.One_Minus_Src_Color = gl.ONE_MINUS_SRC_COLOR,
.Dst_Color = gl.DST_COLOR,
.One_Minus_Dst_Color = gl.ONE_MINUS_DST_COLOR,
// Alpha.
.Src_Alpha = gl.SRC_ALPHA,
.One_Minus_Src_Alpha = gl.ONE_MINUS_SRC_ALPHA,
.Dst_Alpha = gl.DST_ALPHA,
.One_Minus_Dst_Alpha = gl.ONE_MINUS_DST_ALPHA,
}
opengl_set_blending :: proc(renderer: ^Renderer, enable: bool, factors: Blend_Factors) {
if enable {
gl.Enable(gl.BLEND)
}
else {
gl.Disable(gl.BLEND)
// Not sure that I want to return here.. Pretty nice that the blendfunc gets reset correctly even if we're disabling blending.
}
table := OPENGL_BLEND_FACTOR_U32_TABLE
gl.BlendFunc(
sfactor = table[factors.source],
dfactor = table[factors.destination],
)
}
opengl_bind_render_target :: proc(renderer: ^Renderer, rt: ^Render_Target) {
if rt == nil {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
gl.Viewport(
0,
0,
i32(renderer.viewport.width),
i32(renderer.viewport.height),
)
return
}
gl.BindFramebuffer(gl.FRAMEBUFFER, rt.backend.handle)
gl.Viewport(
0,
0,
i32(rt.color_texture.width),
i32(rt.color_texture.height),
)
}
opengl_create_render_target :: proc(renderer: ^Renderer, rt: ^Render_Target) -> bool {
fb: u32
gl.GenFramebuffers(1, &fb)
gl.BindFramebuffer(gl.FRAMEBUFFER, fb)
gl.FramebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
rt.color_texture.backend.handle,
0
)
if rt.depth_texture != nil {
gl.FramebufferTexture2D(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.TEXTURE_2D,
rt.depth_texture.backend.handle,
0
)
}
if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
fmt.printfln("Framebuffer status: %v", gl.CheckFramebufferStatus(gl.FRAMEBUFFER))
return false
}
rt.backend = Render_Target_OpenGL {
handle = fb
}
return true
}
} }

View File

@@ -6,20 +6,61 @@ import "vendor:stb/image"
import "core:fmt" import "core:fmt"
Texture :: struct { Texture :: struct {
width, height, channels, depth: u32, width, height, depth, channels: u16,
format: Texture_Format,
filter_mode: Texture_Filter_Mode,
wrap_mode: Texture_Wrap_Mode,
is_depth: bool,
backend: Texture_Backend, backend: Texture_Backend,
} }
Texture_Format :: enum {
RGBA8,
RGBA16F,
R11G11B10F,
R8,
R16F,
R32F,
Depth16,
Depth24,
Depth32F,
Depth24_Stencil8,
Depth32F_Stencil8,
}
Render_Target :: struct {
color_texture, depth_texture: ^Texture,
backend: Render_Target_Backend,
}
Texture_Filter_Mode :: enum {
Nearest,
Linear,
Nearest_MipMap,
Linear_MipMap,
}
Texture_Wrap_Mode :: enum {
Clamp,
Repeat,
Mirror,
}
create_texture :: proc { create_texture :: proc {
create_texture_from_path, create_texture_from_path,
create_texture_raw,
} }
create_texture_from_path :: proc(renderer: ^Renderer, path: string) -> (Texture, bool) { create_texture_from_path :: proc(renderer: ^Renderer, path: string, filter_mode := Texture_Filter_Mode.Linear, wrap_mode := Texture_Wrap_Mode.Repeat) -> (Texture, bool) {
width, height, depth, channels: c.int width, height, channels, depth: c.int
path_cstr := strings.clone_to_cstring(path) path_cstr := strings.clone_to_cstring(path)
defer delete(path_cstr) defer delete(path_cstr)
image.set_flip_vertically_on_load(1) // NOTE: SS - This should not necessarily happen on all graphics-apis. image.set_flip_vertically_on_load(1)
data := image.load(path_cstr, &width, &height, &channels, desired_channels = 0) data := image.load(path_cstr, &width, &height, &channels, desired_channels = 0)
if data == nil { if data == nil {
return {}, false return {}, false
@@ -27,23 +68,72 @@ create_texture_from_path :: proc(renderer: ^Renderer, path: string) -> (Texture,
depth = 8 depth = 8
t: Texture t: Texture
t.width = u32(width) t.width = u16(width)
t.height = u32(height) t.height = u16(height)
t.channels = u32(channels) t.filter_mode = filter_mode
t.depth = u32(depth) t.wrap_mode = wrap_mode
t.depth = u16(depth)
t.channels = u16(channels)
switch channels {
case 1: {
t.format = .R8
}
case 3: {
t.format = .RGBA8
}
case 4: {
t.format = .RGBA8
}
case: {
t.format = .RGBA8
}
}
when RENDER_BACKEND_OPENGL { when RENDER_BACKEND_OPENGL {
if !opengl_load_texture(renderer, &t, data) { if !opengl_load_texture(renderer, &t, data) {
return {}, false return {}, false
} }
} }
else {
return t, true
}
create_texture_raw :: proc(renderer: ^Renderer, width, height: u16, format: Texture_Format, filter_mode: Texture_Filter_Mode, wrap_mode: Texture_Wrap_Mode, is_depth: bool = false) -> (Texture, bool) { t: Texture
t.width = width
t.height = height
t.format = format
t.filter_mode = filter_mode
t.wrap_mode = wrap_mode
t.is_depth = is_depth
when RENDER_BACKEND_OPENGL {
if !opengl_load_texture(renderer, &t, nil) {
return {}, false
}
} else {
#assert(false) #assert(false)
} }
return t, true return t, true
} }
create_render_target :: proc(renderer: ^Renderer, color_texture, depth_texture: ^Texture) -> (Render_Target, bool) {
rt: Render_Target
rt.color_texture = color_texture
rt.depth_texture = depth_texture
when RENDER_BACKEND_OPENGL {
if !opengl_create_render_target(renderer, &rt) {
return {}, false
}
}
return rt, true
}
delete_texture :: proc(renderer: ^Renderer, texture: ^Texture) { delete_texture :: proc(renderer: ^Renderer, texture: ^Texture) {
when RENDER_BACKEND_OPENGL { when RENDER_BACKEND_OPENGL {
opengl_delete_texture(renderer, texture) opengl_delete_texture(renderer, texture)