diff --git a/material.odin b/material.odin index faa8e71..b11a11a 100644 --- a/material.odin +++ b/material.odin @@ -1,7 +1,16 @@ package renderer Material :: struct { - shader_program: Shader_Program, - texture: Texture, // Diffuse, normal etc later. + // 'Name'? + shader_program: ^Shader_Program, + texture: ^Texture, // Diffuse, normal etc later. // 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 } \ No newline at end of file diff --git a/mesh.odin b/mesh.odin index b309b22..15fd599 100644 --- a/mesh.odin +++ b/mesh.odin @@ -8,8 +8,13 @@ Mesh :: struct { Mesh_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 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)) 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 { return {}, false } diff --git a/pass.odin b/pass.odin index fe45475..f710496 100644 --- a/pass.odin +++ b/pass.odin @@ -2,35 +2,155 @@ package renderer 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, - clear_color: RGB_Color, - should_test_depth: bool, - should_clear_depth: bool, + input_textures: [PASS_MAX_INPUT_TEXTURES]^Texture, // TODO: SS - Make this a map? + input_texture_count: u8, - draw_commands: [MAX_DRAW_COMMANDS_PER_PASS]Draw_Command, - draw_command_count: u32, + output_rt: ^Render_Target, + + 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 { + renderer: ^Renderer, // Needed for sorting. + mesh: Mesh, material: Material, position: [3]f32, rotation: [3]f32, scale: [3]f32, - // TODO: SS - Add rotation. } -add_command_to_pass :: proc(pass: ^Pass, command: Draw_Command) -> bool { - if pass.draw_command_count >= len(pass.draw_commands) { - return false +Blend_Mode :: enum { + None, + 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, + } + + for texture, i in input_textures { + 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 } - pass.draw_commands[pass.draw_command_count] = command - pass.draw_command_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 } \ No newline at end of file diff --git a/pipeline.odin b/pipeline.odin index 7f1b67d..c1c86c0 100644 --- a/pipeline.odin +++ b/pipeline.odin @@ -3,45 +3,92 @@ package renderer import "core:mem" import "core:log" -MAX_PASSES_IN_PIPELINE :: 8 +PIPELINE_MAX_SCENE_PASSES :: 8 +PIPELINE_MAX_POST_PROCESSING_PASSES :: 8 Pipeline :: struct { - passes: [MAX_PASSES_IN_PIPELINE]^Pass, - amount_of_passes: u8, + scene_passes: [PIPELINE_MAX_SCENE_PASSES]^Scene_Pass, + 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) { assert(renderer != nil) - pipeline := &renderer.pipeline + p := &renderer.pipeline - pipeline.amount_of_passes = 0; - mem.set(&pipeline.passes[0], 0, MAX_PASSES_IN_PIPELINE * size_of(^Pass)) + p.amount_of_scene_passes = 0; + 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( + renderer: ^Renderer, + fullscreen_material: ^Material, fullscreen_mesh: ^Mesh, + scene_passes: []^Scene_Pass, + post_processing_passes: []^Post_Processing_Pass, +){ + assert(renderer != nil) + clear_pipeline(renderer) - for p in passes { + + renderer.pipeline.fullscreen_material = fullscreen_material + renderer.pipeline.fullscreen_mesh = fullscreen_mesh + + for p in scene_passes { if !add_pass_to_pipeline(renderer, p) { - return + break + } + } + + for p in post_processing_passes { + if !add_pass_to_pipeline(renderer, p) { + break } } } -@(private="file") add_pass_to_pipeline :: proc(renderer: ^Renderer, pass: ^Pass) -> bool { +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(pass != nil) + assert(scene_pass != nil) pipeline := &renderer.pipeline - if pipeline.amount_of_passes == MAX_PASSES_IN_PIPELINE { - log.warnf("Failed to add pass '%v' to renderer's pipeline; hit max capacity (%v).", pass.name, MAX_PASSES_IN_PIPELINE) + if pipeline.amount_of_scene_passes == PIPELINE_MAX_SCENE_PASSES { + 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 } - pipeline.passes[pipeline.amount_of_passes] = pass - pipeline.amount_of_passes += 1 + pipeline.scene_passes[pipeline.amount_of_scene_passes] = scene_pass + 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 } \ No newline at end of file diff --git a/renderer.odin b/renderer.odin index 02b7ef5..625d327 100644 --- a/renderer.odin +++ b/renderer.odin @@ -1,5 +1,7 @@ package renderer +import "core:slice" +import "core:sort" import "core:math/linalg" import "core:fmt" 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 { - opengl_clear_screen(renderer, clear_depth) + opengl_clear_screen(renderer, clear_color, clear_depth) } } @@ -87,54 +89,103 @@ render_frame :: proc(renderer: ^Renderer) { return } - view_matrix, view_matrix_ok := get_camera_view_matrix(renderer.active_camera) - assert(view_matrix_ok) - projection_matrix, projection_matrix_ok := get_camera_projection_matrix(renderer, renderer.active_camera) - assert(projection_matrix_ok) + view_matrix, _ := get_camera_view_matrix(renderer.active_camera) + projection_matrix, _ := get_camera_projection_matrix(renderer, renderer.active_camera) pipeline := &renderer.pipeline - for i in 0 ..< pipeline.amount_of_passes { - pass := pipeline.passes[i] - set_clear_color(renderer, pass.clear_color) - clear_screen(renderer, pass.should_clear_depth) - enable_depth_testing(renderer, pass.should_test_depth) + for i in 0 ..< pipeline.amount_of_scene_passes { + execute_scene_pass(renderer, pipeline.scene_passes[i], view_matrix, projection_matrix) + } - for i in 0.. 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) } } \ No newline at end of file diff --git a/renderer_backend_opengl_windows.odin b/renderer_backend_opengl_windows.odin index d5625f3..6a094fb 100644 --- a/renderer_backend_opengl_windows.odin +++ b/renderer_backend_opengl_windows.odin @@ -101,14 +101,15 @@ when RENDER_BACKEND_OPENGL { ) } - opengl_clear_screen :: proc(renderer: ^Renderer, clear_depth: bool) { - mask := u32(gl.COLOR_BUFFER_BIT) - if clear_depth { - gl.ClearDepth(1.0) - mask |= gl.DEPTH_BUFFER_BIT + opengl_clear_screen :: proc(renderer: ^Renderer, clear_color: bool, clear_depth: bool) { + flags := u32(0) + if clear_color { + flags |= gl.COLOR_BUFFER_BIT } - - gl.Clear(mask) + if clear_depth { + flags |= gl.DEPTH_BUFFER_BIT + } + gl.Clear(flags) } opengl_swap_buffers :: proc(renderer: ^Renderer) { @@ -127,7 +128,8 @@ when RENDER_BACKEND_OPENGL { Shader_Backend :: Shader_OpenGL Shader_Program_Backend :: Shader_Program_OpenGL Texture_Backend :: Texture_OpenGL - + Render_Target_Backend :: Render_Target_OpenGL + Mesh_OpenGL :: struct { vbo, vao, ebo: u32, } @@ -144,7 +146,11 @@ when RENDER_BACKEND_OPENGL { 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) m: Mesh_OpenGL @@ -161,14 +167,20 @@ when RENDER_BACKEND_OPENGL { 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.VertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 8 * size_of(f32), uintptr(0)) - gl.EnableVertexAttribArray(0) + layout_total_stride := u32(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))) - gl.EnableVertexAttribArray(1) + offset := u32(0) + 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.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]) } + 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) { gl.BindVertexArray(mesh.backend.vao) assert(mesh.amount_of_indices < u32(max(i32))) 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 { handle: u32 gl.GenTextures(1, &handle) gl.BindTexture(gl.TEXTURE_2D, handle) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_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 + switch t.filter_mode { + 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_MAG_FILTER, gl.LINEAR) } - format := gl.RGBA - switch t.channels { - case 1: format = gl.RED - case 2: format = gl.RG - case 3: format = gl.RGB - case 4: format = gl.RGBA + switch t.wrap_mode { + case .Clamp: + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP) + case .Repeat: + 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 - - 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)) + internal_format, format, type := opengl_texture_format_from_texture(t) gl.TexImage2D( - target = gl.TEXTURE_2D, - level = 0, - internalformat = i32(internal), - width = i32(t.width), - height = i32(t.height), - border = 0, - format = u32(format), - type = u32(type), - pixels = pixels, - // pixels = &pixels[0] - // pixels = &bytes.reverse(bytes.buffer_to_bytes(&img.pixels))[0] + gl.TEXTURE_2D, + 0, + i32(internal_format), + i32(t.width), + i32(t.height), + 0, + u32(format), + u32(type), + pixels, ) - gl.GenerateMipmap(gl.TEXTURE_2D) + if !t.is_depth { + gl.GenerateMipmap(gl.TEXTURE_2D) + } t.backend = Texture_OpenGL { handle = handle } return true @@ -335,12 +428,105 @@ when RENDER_BACKEND_OPENGL { gl.DeleteTextures(1, &t.backend.handle) } - opengl_enable_depth_testing :: proc(renderer: ^Renderer, enable: bool) { - if enable { + opengl_apply_depth :: proc(renderer: ^Renderer, test_depth, write_depth: bool) { + if test_depth { gl.Enable(gl.DEPTH_TEST) } else { 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 + } + } \ No newline at end of file diff --git a/texture.odin b/texture.odin index a3ff4c3..b0f819e 100644 --- a/texture.odin +++ b/texture.odin @@ -6,44 +6,134 @@ import "vendor:stb/image" import "core:fmt" 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, } +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_from_path, + create_texture_raw, } -create_texture_from_path :: proc(renderer: ^Renderer, path: string) -> (Texture, bool) { - width, height, depth, channels: c.int - - path_cstr := strings.clone_to_cstring(path) - defer delete(path_cstr) - image.set_flip_vertically_on_load(1) // NOTE: SS - This should not necessarily happen on all graphics-apis. - data := image.load(path_cstr, &width, &height, &channels, desired_channels = 0) - if data == nil { - return {}, false - } - depth = 8 +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, channels, depth: c.int + + path_cstr := strings.clone_to_cstring(path) + defer delete(path_cstr) + image.set_flip_vertically_on_load(1) + data := image.load(path_cstr, &width, &height, &channels, desired_channels = 0) + if data == nil { + return {}, false + } + depth = 8 + + t: Texture + t.width = u16(width) + t.height = u16(height) + t.filter_mode = filter_mode + 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 { + if !opengl_load_texture(renderer, &t, data) { + return {}, false + } + } + + 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 - t: Texture - t.width = u32(width) - t.height = u32(height) - t.channels = u32(channels) - t.depth = u32(depth) - when RENDER_BACKEND_OPENGL { - if !opengl_load_texture(renderer, &t, data) { + if !opengl_load_texture(renderer, &t, nil) { + return {}, false + } + } else { + #assert(false) + } + + 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 } } - else { - #assert(false) - } - - return t, true + + return rt, true } + delete_texture :: proc(renderer: ^Renderer, texture: ^Texture) { when RENDER_BACKEND_OPENGL { opengl_delete_texture(renderer, texture)