From 3d1d31538d30a7f161f9f2b6d5e075ec69d3b860 Mon Sep 17 00:00:00 2001 From: iamcheeseman <[hidden email]> Date: Tue, 3 Feb 2026 22:25:00 -0500 Subject: ditch raylib (icky, and for loser BEGINNERS) --- src/fw/fw.odin | 114 +++++++++++++++ src/fw/input.odin | 76 ++++++++++ src/fw/keys.odin | 140 ++++++++++++++++++ src/fw/opengl.odin | 363 +++++++++++++++++++++++++++++++++++++++++++++++ src/fw/renderer.odin | 390 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/fw/shader.frag | 16 +++ src/fw/shader.vert | 18 +++ 7 files changed, 1117 insertions(+) create mode 100644 src/fw/fw.odin create mode 100644 src/fw/input.odin create mode 100644 src/fw/keys.odin create mode 100644 src/fw/opengl.odin create mode 100644 src/fw/renderer.odin create mode 100644 src/fw/shader.frag create mode 100644 src/fw/shader.vert (limited to 'src/fw') diff --git a/src/fw/fw.odin b/src/fw/fw.odin new file mode 100644 index 0000000..86a3e3a --- /dev/null +++ b/src/fw/fw.odin @@ -0,0 +1,114 @@ +package fw + +import "core:log" + +import "vendor:glfw" + +Vec2 :: [2]f32 +Vec2i :: [2]i32 +Vec3 :: [3]f32 +Vec4 :: [4]f32 +Color :: [4]f32 +Mat4 :: matrix[4, 4]f32 + +Rect :: struct { + start: Vec2, + size: Vec2, +} + +Config :: struct { + win_name: cstring, + win_size: Vec2i, + screen_size: Vec2i, + renderer_backend: Renderer_Backend, +} + +@(private) window: glfw.WindowHandle +@(private) last_frame: f64 +@(private) dt: f64 + +rgba8 :: proc(r: u8, g: u8, b: u8, a: u8 = 255) -> Color { + return Color{ + f32(r) / 255, + f32(g) / 255, + f32(b) / 255, + f32(a) / 255, + } +} + +init :: proc(config: Config) { + if !glfw.Init() { + panic("could not init glfw window") + } + + glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3) + glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3) + glfw.WindowHint(glfw.RESIZABLE, false) + glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) + + window = glfw.CreateWindow( + config.win_size.x, + config.win_size.y, + config.win_name, + nil, + nil, + ) + + if window == nil { + panic("could not create glfw window") + } + + glfw.SwapInterval(1) + + _renderer_init(config) +} + +deinit :: proc() { + _renderer_deinit() + + glfw.DestroyWindow(window) + glfw.Terminate() +} + +next_frame :: proc() -> bool { + // End the previous frame + if can_flush() { + flush_batch(&renderer.draw_call) + new_batch(renderer.white_1x1.handle, .None, false) + } + + renderer.vt.end_frame() + new_batch(renderer.white_1x1.handle, .None, false) + + renderer.draw_calls_count = renderer._draw_call_counter + renderer._draw_call_counter = 0 + + glfw.SwapBuffers(window) + glfw.PollEvents() + _update_input() + + // log.debugf("Draw calls: %v", renderer.draw_calls_count) + + // Begin next frame + + time := get_time() + dt = time - last_frame + last_frame = time + + renderer.vt.clear({0, 0, 0, 1}) + + renderer.vt.start_frame() + renderer.vt.clear(renderer.bg_col) + + return !bool(glfw.WindowShouldClose(window)) +} + +@(require_results) +get_time :: #force_inline proc() -> f64 { + return glfw.GetTime() +} + +@(require_results) +get_delta_time :: #force_inline proc() -> f64 { + return dt +} diff --git a/src/fw/input.odin b/src/fw/input.odin new file mode 100644 index 0000000..a3cbe25 --- /dev/null +++ b/src/fw/input.odin @@ -0,0 +1,76 @@ +package fw + +import "core:c" +import "vendor:glfw" + +Keyboard_Input :: union { + Key, + Mouse_Button, +} + +Controller_Input :: union { +} + +Keybind :: struct { + input: Keyboard_Input, + pressed: bool, + just_pressed: bool, +} + +key_down_last_frame: #sparse [Key]bool +key_just_down: #sparse [Key]bool + +mouse_down_last_frame: #sparse [Mouse_Button]bool +mouse_just_down: #sparse [Mouse_Button]bool + +@(private) +_update_input :: proc() { + for key in Key { + status := glfw.GetKey(window, c.int(key)) + key_pressed := status == glfw.PRESS + key_just_down[key] = key_pressed && !key_down_last_frame[key] + + key_down_last_frame[key] = key_pressed + } + + for mb in Mouse_Button { + status := glfw.GetMouseButton(window, c.int(mb)) + mouse_pressed := status == glfw.PRESS + mouse_just_down[mb] = mouse_pressed && !mouse_down_last_frame[mb] + + mouse_down_last_frame[mb] = mouse_pressed + } +} + +is_keybind_down :: proc(keybind: Keybind) -> bool { + switch val in keybind.input { + case Key: + return glfw.GetKey(window, c.int(val)) == glfw.PRESS + case Mouse_Button: + return glfw.GetMouseButton(window, c.int(val)) == glfw.PRESS + } + + assert(false) + return false +} + +is_keybind_just_down :: proc(keybind: Keybind) -> bool { + switch val in keybind.input { + case Key: return key_just_down[val] + case Mouse_Button: return mouse_just_down[val] + } + + assert(false) + return false +} + +get_mouse_pos :: proc() -> (mouse_pos: Vec2) { + w_width, w_height := glfw.GetWindowSize(window) + mx, my := glfw.GetCursorPos(window) + + mouse_pos = Vec2{f32(mx), f32(mx)} + mouse_pos /= Vec2{f32(w_width), f32(w_height)} + mouse_pos *= Vec2{f32(framebuf.color.size.x), f32(framebuf.color.size.y)} + // mouse_pos += state.camera.target + return +} diff --git a/src/fw/keys.odin b/src/fw/keys.odin new file mode 100644 index 0000000..180cb4b --- /dev/null +++ b/src/fw/keys.odin @@ -0,0 +1,140 @@ +package fw + +Mouse_Button :: enum { + B1 = 0, + B2 = 1, + B3 = 2, + B4 = 3, + B5 = 4, + B6 = 5, + B7 = 6, + B8 = 7, + Last = B8, + Left = B1, + Right = B2, + Middle = B3, +} + +Key :: enum { + Space = 32, + Apostrophe = 39, + Comma = 44, + Minus = 45, + Period = 46, + Slash = 47, + Semicolon = 59, + Equal = 61, + Left_Bracket = 91, + Backslash = 92, + Right_Bracket = 93, + Grave_Accent = 96, + World_1 = 161, + World_2 = 162, + N0 = 48, + N1 = 49, + N2 = 50, + N3 = 51, + N4 = 52, + N5 = 53, + N6 = 54, + N7 = 55, + N8 = 56, + N9 = 57, + A = 65, + B = 66, + C = 67, + D = 68, + E = 69, + F = 70, + G = 71, + H = 72, + I = 73, + J = 74, + K = 75, + L = 76, + M = 77, + N = 78, + O = 79, + P = 80, + Q = 81, + R = 82, + S = 83, + T = 84, + U = 85, + V = 86, + W = 87, + X = 88, + Y = 89, + Z = 90, + Escape = 256, + Enter = 257, + Tab = 258, + Backspace = 259, + Insert = 260, + Delete = 261, + Right = 262, + Left = 263, + Down = 264, + Up = 265, + Page_Up = 266, + Page_Down = 267, + Home = 268, + End = 269, + Caps_Lock = 280, + Scroll_Lock = 281, + Num_Lock = 282, + Print_Screen = 283, + Pause = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + Kp_0 = 320, + Kp_1 = 321, + Kp_2 = 322, + Kp_3 = 323, + Kp_4 = 324, + Kp_5 = 325, + Kp_6 = 326, + Kp_7 = 327, + Kp_8 = 328, + Kp_9 = 329, + Kp_Decimal = 330, + Kp_Divide = 331, + Kp_Multiply = 332, + Kp_Subtract = 333, + Kp_Add = 334, + Kp_Enter = 335, + Kp_Equal = 336, + Left_Shift = 340, + Left_Control = 341, + Left_Alt = 342, + Left_Super = 343, + Right_Shift = 344, + Right_Control = 345, + Right_Alt = 346, + Right_Super = 347, + Menu = 348, + Last = Menu, +} diff --git a/src/fw/opengl.odin b/src/fw/opengl.odin new file mode 100644 index 0000000..ab2bb81 --- /dev/null +++ b/src/fw/opengl.odin @@ -0,0 +1,363 @@ +package fw + +import "base:runtime" + +import "core:log" +import "core:fmt" +import "core:math/linalg" +import "core:os" + +import "vendor:glfw" +import gl "vendor:OpenGL" + +@(private) +enum_conv: map[All_Enums]u32 + +vert_shader_src := string(#load("shader.vert")) +frag_shader_src := string(#load("shader.frag")) + +vao: u32 +vbo: u32 +ebo: u32 + +framebuf: struct { + handle: u32, + color: Texture, // so we can reuse pre-existing draw procs + depth: u32, +} +shader_program: u32 + +projection_loc: i32 +view_loc: i32 +tex0_loc: i32 + +// @(private) +// _gl_debug_message :: proc "c" ( +// source: u32, +// type: u32, +// id: u32, +// severity: u32, +// length: i32, +// message: cstring, +// userParam: rawptr, +// ) { +// context = runtime.default_context() +// switch severity { +// case gl.DEBUG_SEVERITY_HIGH: fmt.print("[HIGH]") +// case gl.DEBUG_SEVERITY_MEDIUM: fmt.print("[MEDIUM]") +// case gl.DEBUG_SEVERITY_LOW: fmt.print("[LOW]") +// case gl.DEBUG_SEVERITY_NOTIFICATION: fmt.print("[NOTIF]") +// } +// fmt.printfln(" OpenGL %v: %v", id, message) +// } + +@(private) +_init_vertex_buffer_and_array :: proc(vao: u32, vbo: u32, ebo: u32) { + gl.BindVertexArray(vao) + + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.BufferData(gl.ARRAY_BUFFER, 0, nil, gl.DYNAMIC_DRAW) + + gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo) + gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, 0, nil, gl.DYNAMIC_DRAW) + + stride := i32(size_of(Vertex)) + + gl.EnableVertexAttribArray(0) + gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, offset_of(Vertex, pos)) + + gl.EnableVertexAttribArray(1) + gl.VertexAttribPointer(1, 2, gl.FLOAT, false, stride, offset_of(Vertex, uv)) + + gl.EnableVertexAttribArray(2) + gl.VertexAttribPointer( + 2, + 4, + gl.FLOAT, + false, + stride, + offset_of(Vertex, color), + ) +} + +@(private) +_gl_load_shader :: proc(src: string, type: u32) -> u32 { + shader := gl.CreateShader(type) + csrc := cstring(raw_data(src)) + src_len := i32(len(src)) + gl.ShaderSource(shader, 1, &csrc, &src_len) + gl.CompileShader(shader) + + status: i32 + gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status) + if status == 0 { + gl.DeleteShader(shader) + return 0 + } + + return shader +} + +@(private) +_gl_load_shader_program :: proc(vert_src: string, frag_src: string) -> u32 { + vert_shader := _gl_load_shader(vert_src, gl.VERTEX_SHADER) + defer gl.DeleteShader(vert_shader) + frag_shader := _gl_load_shader(frag_src, gl.FRAGMENT_SHADER) + defer gl.DeleteShader(frag_shader) + + program := gl.CreateProgram() + gl.AttachShader(program, vert_shader) + gl.AttachShader(program, frag_shader) + gl.LinkProgram(program) + + status: i32 + gl.GetProgramiv(program, gl.LINK_STATUS, &status) + if status == 0 { + gl.DeleteProgram(program) + return 0 + } + + return program +} + +gl_init :: proc() { + glfw.MakeContextCurrent(window) + + gl.load_up_to(3, 3, glfw.gl_set_proc_address) + + gl.Enable(gl.DEPTH_TEST) + gl.DepthFunc(gl.LEQUAL) + + // gl.Enable(gl.BLEND); + // gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + renderer.vt.start_frame = gl_start_frame + renderer.vt.end_frame = gl_end_frame + renderer.vt.draw = gl_draw + renderer.vt.clear = gl_clear + renderer.vt.init_texture = gl_init_texture + renderer.vt.destroy_texture = gl_destroy_texture + renderer.vt.init_screen = gl_init_screen + + enum_conv[Vertex_Mode.None] = gl.TRIANGLES + enum_conv[Vertex_Mode.Triangles] = gl.TRIANGLES + enum_conv[Vertex_Mode.Triangle_Fan] = gl.TRIANGLE_FAN + enum_conv[Vertex_Mode.Triangle_Strip] = gl.TRIANGLE_STRIP + enum_conv[Vertex_Mode.Lines] = gl.LINES + enum_conv[Vertex_Mode.Line_Strip] = gl.LINE_STRIP + enum_conv[Vertex_Mode.Line_Loop] = gl.LINE_LOOP + enum_conv[Vertex_Mode.Points] = gl.POINTS + + gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1); + + gl.GenBuffers(1, &vbo) + gl.GenBuffers(1, &ebo) + gl.GenVertexArrays(1, &vao) + _init_vertex_buffer_and_array(vao, vbo, ebo) + + shader_program = _gl_load_shader_program(vert_shader_src, frag_shader_src) + projection_loc = gl.GetUniformLocation(shader_program, "projection") + view_loc = gl.GetUniformLocation(shader_program, "view") + tex0_loc = gl.GetUniformLocation(shader_program, "tex0") +} + +gl_deinit :: proc() { + delete(enum_conv) + + destroy_texture(framebuf.color) + gl.DeleteRenderbuffers(1, &framebuf.depth) + gl.DeleteFramebuffers(1, &framebuf.handle) + + gl.DeleteProgram(shader_program) + + gl.DeleteBuffers(1, &vbo) + gl.DeleteVertexArrays(1, &vao) +} + +gl_start_frame :: proc() { + gl.BindFramebuffer(gl.FRAMEBUFFER, framebuf.handle) + gl.Viewport(0, 0, framebuf.color.size.x, framebuf.color.size.y) + renderer.projection = linalg.matrix_ortho3d( + f32(0), f32(framebuf.color.size.x), + f32(framebuf.color.size.y), 0, + -1, 1, + ) +} + +gl_end_frame :: proc() { + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) + + w_width, w_height := glfw.GetWindowSize(window) + + gl.Viewport(0, 0, w_width, w_height) + + renderer.projection = linalg.matrix_ortho3d( + f32(0), f32(w_width), + 0, f32(w_height), + -1, 1, + ) + + draw_rect({0, 0}, {f32(w_width), f32(w_height)}, tex = framebuf.color) + flush_batch(&renderer.draw_call) +} + +gl_draw :: proc(draw_call: Draw_Call) { + draw_call := draw_call + + gl.UseProgram(shader_program) + + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, draw_call.tex) + + gl.Uniform1i(tex0_loc, 0) + gl.UniformMatrix4fv( + projection_loc, + 1, + false, + transmute([^]f32)(&draw_call.projection), + ) + gl.UniformMatrix4fv(view_loc, 1, false, transmute([^]f32)(&draw_call.view)) + + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.BufferData( + gl.ARRAY_BUFFER, + size_of(Vertex) * len(draw_call.vertices), + raw_data(draw_call.vertices), + gl.DYNAMIC_DRAW, + ) + + gl.BindVertexArray(vao) + + if !draw_call.indexed { + gl.DrawArrays( + enum_conv[draw_call.vertex_mode], + 0, + i32(len(draw_call.vertices)), + ) + } else { + gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo) + gl.BufferData( + gl.ELEMENT_ARRAY_BUFFER, + size_of(Index) * len(draw_call.indices), + raw_data(draw_call.indices), + gl.DYNAMIC_DRAW, + ) + + gl.DrawElements( + enum_conv[draw_call.vertex_mode], + i32(len(draw_call.indices)), + gl.UNSIGNED_SHORT, + nil, + ) + gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0) + } + + gl.BindBuffer(gl.ARRAY_BUFFER, 0) + gl.BindVertexArray(0) +} + +gl_clear :: proc(col: Color) { + gl.ClearColor(col.r, col.g, col.b, col.a) + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) +} + +gl_init_texture :: proc( + texture: ^Texture, + data: []u8, + channels: i32, +) { + tex: u32 + gl.GenTextures(1, &tex) + + format := gl.RGBA + switch channels { + case 3: format = gl.RGB + case 4: format = gl.RGBA + case: panic("Unsupported amount of channels. Must be either 3 or 4.") + } + + gl.BindTexture(gl.TEXTURE_2D, tex) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + i32(format), + texture.size.x, + texture.size.y, + 0, + u32(format), + gl.UNSIGNED_BYTE, + raw_data(data), + ) + + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + + gl.GenerateMipmap(gl.TEXTURE_2D) + + texture.handle = tex +} + +gl_destroy_texture :: proc(texture: Texture_Handle) { + texture := texture + gl.DeleteTextures(1, &texture) +} + +gl_init_screen :: proc(screen_size: Vec2i) { + assert(screen_size.x * screen_size.y > 0) + + gl.GenFramebuffers(1, &framebuf.handle) + gl.BindFramebuffer(gl.FRAMEBUFFER, framebuf.handle) + + color_tex: u32 + gl.GenTextures(1, &color_tex) + gl.BindTexture(gl.TEXTURE_2D, color_tex) + + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGB, + screen_size.x, + screen_size.y, + 0, + gl.RGB, + gl.UNSIGNED_BYTE, + nil, + ) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + + gl.FramebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + color_tex, + 0, + ) + + framebuf.color = Texture { + handle = color_tex, + size = screen_size, + } + + gl.GenRenderbuffers(1, &framebuf.depth) + gl.BindRenderbuffer(gl.RENDERBUFFER, framebuf.depth) + gl.RenderbufferStorage( + gl.RENDERBUFFER, + gl.DEPTH_COMPONENT32F, + screen_size.x, + screen_size.y, + ); + gl.FramebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, + framebuf.depth, + ); + + if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE { + log.fatal("[OPENGL] Framebuffer could not be completed") + os.exit(1) + } + + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) +} diff --git a/src/fw/renderer.odin b/src/fw/renderer.odin new file mode 100644 index 0000000..082a0d3 --- /dev/null +++ b/src/fw/renderer.odin @@ -0,0 +1,390 @@ +package fw + +import "core:log" +import "core:image" +import "core:math" +import "core:math/linalg" +import "core:math/linalg/glsl" + +Index :: u16 + +Renderer_Backend :: enum { + OpenGL, +} + +Vertex_Mode :: enum { + None, + Triangles, + Triangle_Strip, + Triangle_Fan, + Lines, + Line_Strip, + Line_Loop, + Points, +} + +Blend_Mode :: enum { + Alpha, + Additive, +} + +@(private) +All_Enums :: union { + Vertex_Mode, +} + +VTable :: struct { + start_frame: proc(), + end_frame: proc(), + draw: proc(Draw_Call), + clear: proc(Color), + init_texture: proc( + texture: ^Texture, + data: []u8, + channels: i32, + ), + destroy_texture: proc(texture: Texture_Handle), + init_screen: proc(screen_size: Vec2i), +} + +Texture_Handle :: u32 + +Texture :: struct { + handle: Texture_Handle, // for opengl, this is just the actual handle. for + // others, it can be an index to the real handle. + size: Vec2i, +} + +Vertex :: struct { + pos: Vec3, + uv: Vec2, + color: Vec4, +} + +Draw_Call :: struct { + tex: Texture_Handle, + vertex_mode: Vertex_Mode, + model_mat: [dynamic]Mat4, + vertices: [dynamic]Vertex, + indices: [dynamic]Index, + + projection: Mat4, + view: Mat4, + + indexed: bool, +} + +renderer: struct { + draw_call: Draw_Call, + backend: Renderer_Backend, + bg_col: Color, + white_1x1: Texture, + projection: Mat4, + view: Mat4, + + draw_calls_count: i32, + _draw_call_counter: i32, + + vt: VTable, +} = {} + +@(private) +_renderer_init :: proc(config: Config) { + renderer.bg_col = Color{0.2, 0.2, 0.2, 1} + + renderer.projection = linalg.matrix_ortho3d(f32(0), 800, 800, 0, 0, 1) + renderer.view = linalg.identity(Mat4) + + renderer.backend = config.renderer_backend + + switch config.renderer_backend{ + case .OpenGL: gl_init() + } + + white_1x1_data := []u8{255, 255, 255} + renderer.white_1x1 = create_texture(white_1x1_data, {1, 1}, 3) + + renderer.vt.init_screen(config.screen_size) +} + +@(private) +_renderer_deinit :: proc() { + destroy_texture(renderer.white_1x1) + + switch renderer.backend { + case .OpenGL: gl_deinit() + } +} + +flush_batch :: proc(draw_call: ^Draw_Call) { + renderer.vt.draw(draw_call^) + renderer._draw_call_counter += 1 +} + +new_batch :: proc( + tex: Texture_Handle, + vertex_mode: Vertex_Mode, + indexed: bool, +) { + renderer.draw_call.tex = tex + renderer.draw_call.vertex_mode = vertex_mode + renderer.draw_call.indexed = indexed + renderer.draw_call.projection = renderer.projection + renderer.draw_call.view = renderer.view + + clear(&renderer.draw_call.vertices) + clear(&renderer.draw_call.indices) +} + +@(private="file") +_add_vertex :: #force_inline proc(v: Vertex) { + append(&renderer.draw_call.vertices, v) +} + +@(private="file") +_add_index :: #force_inline proc(i: u16, start: Index) { + append(&renderer.draw_call.indices, start + i) +} + +can_flush :: proc() -> bool { + return renderer.draw_call.vertex_mode != .None && + len(renderer.draw_call.vertices) != 0 && + len(renderer.draw_call.indices) != 0 +} + +try_batch_calls :: proc( + tex: Texture_Handle, + vertex_mode: Vertex_Mode, + indexed: bool, +) { + cant_flush := !can_flush() + if cant_flush { + new_batch(tex, vertex_mode, indexed) + return + } + + can_batch := tex == renderer.draw_call.tex && + vertex_mode == renderer.draw_call.vertex_mode && + indexed == renderer.draw_call.indexed + if !can_batch { + flush_batch(&renderer.draw_call) + new_batch(tex, vertex_mode, indexed) + } +} + +create_texture_from_image :: #force_inline proc(img: ^image.Image) -> Texture { + return create_texture( + img.pixels.buf[:], + {i32(img.width), i32(img.height)}, + i32(img.channels), + ) +} + +create_texture :: proc(data: []u8, size: Vec2i, channels: i32) -> Texture { + texture := Texture{ + size = size, + } + renderer.vt.init_texture(&texture, data, channels) + return texture +} + +destroy_texture :: proc(texture: Texture) { + renderer.vt.destroy_texture(texture.handle) +} + +draw_line :: proc( + start: Vec2, + end: Vec2, + depth: f32 = 0, + color := Color{1, 1, 1, 1}, +) { + try_batch_calls(renderer.white_1x1.handle, .Lines, true) + + reserve(&renderer.draw_call.vertices, len(renderer.draw_call.vertices) + 2) + reserve(&renderer.draw_call.indices, len(renderer.draw_call.indices) + 2) + + start_idx := Index(len(renderer.draw_call.vertices)) + + _add_vertex(Vertex{{start.x, start.y, depth}, {0.5, 0.5}, color}) + _add_vertex(Vertex{{end.x, end.y, depth}, {0.5, 0.5}, color}) + + _add_index(start_idx, 0) + _add_index(start_idx, 1) +} + +draw_rect :: proc( + pos: Vec2, + size: Vec2, + depth: f32 = 0, + color := Color{1, 1, 1, 1}, + tex := renderer.white_1x1, + lines := false, +) { + try_batch_calls(tex.handle, .Triangles if !lines else .Lines, true) + + depth := depth + + if depth < -1 || depth > 1 { + log.warnf( + "Depth is out of range (%v). It will be clamped to -1..1.", + depth + ) + depth = math.clamp(depth, -1, 1) + } + + pos := linalg.round(pos) + + tl := Vertex{{pos.x, pos.y, depth}, {0, 0}, color} + tr := Vertex{{pos.x + size.x, pos.y, depth}, {1, 0}, color} + bl := Vertex{{pos.x, pos.y + size.y, depth}, {0, 1}, color} + br := Vertex{{pos.x + size.x, pos.y + size.y, depth}, {1, 1}, color} + + start := Index(len(renderer.draw_call.vertices)) + + reserve(&renderer.draw_call.vertices, len(renderer.draw_call.vertices) + 4) + _add_vertex(tl) + _add_vertex(tr) + _add_vertex(bl) + _add_vertex(br) + + + if lines { + reserve(&renderer.draw_call.indices, len(renderer.draw_call.indices) + 8) + + _add_index(start, 0) + _add_index(start, 1) + _add_index(start, 1) + _add_index(start, 3) + _add_index(start, 3) + _add_index(start, 2) + _add_index(start, 2) + _add_index(start, 0) + } else { + reserve(&renderer.draw_call.indices, len(renderer.draw_call.indices) + 6) + + _add_index(start, 0) + _add_index(start, 1) + _add_index(start, 2) + _add_index(start, 2) + _add_index(start, 1) + _add_index(start, 3) + } +} + +draw_tex :: proc( + tex: Texture, + pos: Vec2, + depth: f32 = 0, + scale := Vec2{1, 1}, + offset := Vec2{0, 0}, + color := Color{1, 1, 1, 1} +) { + draw_rect( + pos = pos - offset, + size = Vec2{f32(tex.size.x), f32(tex.size.y)} * scale, + depth = depth, + tex = tex, + color = color, + ) +} + +draw_tile :: proc( + tex: Texture, + pos: Vec2, + rect: Rect, + depth: f32 = 0, + color := Color{1, 1, 1, 1} +) { + pos := linalg.round(pos) + + tex_size := Vec2{f32(tex.size.x), f32(tex.size.y)} + + uv_start := rect.start / tex_size + uv_end := (rect.start + rect.size) / tex_size + + size := rect.size + tl := Vertex{{pos.x, pos.y, depth}, {uv_start.x, uv_start.y}, color} + tr := Vertex{{pos.x + size.x, pos.y, depth}, {uv_end.x, uv_start.y}, color} + bl := Vertex{{pos.x, pos.y + size.y, depth}, {uv_start.x, uv_end.y}, color} + br := Vertex{{pos.x + size.x, pos.y + size.y, depth}, {uv_end.x, uv_end.y}, color} + + try_batch_calls(tex.handle, .Triangles, true) + + reserve(&renderer.draw_call.indices, len(renderer.draw_call.indices) + 6) + reserve(&renderer.draw_call.vertices, len(renderer.draw_call.vertices) + 4) + + start := Index(len(renderer.draw_call.vertices)) + + _add_vertex(tl) + _add_vertex(tr) + _add_vertex(bl) + _add_vertex(br) + + _add_index(start, 0) + _add_index(start, 1) + _add_index(start, 2) + _add_index(start, 2) + _add_index(start, 1) + _add_index(start, 3) +} + +draw_tex_ex :: proc( + tex: Texture, + pos: Vec2, + depth: f32 = 0, + rot: f32 = 0, + scale := Vec2{1, 1}, + offset := Vec2{0, 0}, + shear := Vec2{0, 0}, + rect: Maybe(Rect) = nil, + color := Color{1, 1, 1, 1} +) { + tex_size := Vec2{f32(tex.size.x), f32(tex.size.y)} + rect := rect.? or_else Rect{{0, 0}, tex_size} + + pos := linalg.round(pos) + + pos_m: Mat4 = linalg.matrix4_translate(Vec3{pos.x, pos.y, 0}) + rotation_m: Mat4 = linalg.matrix4_rotate(rot, Vec3{0, 0, 1}) + scale_m: Mat4 = linalg.matrix4_scale(Vec3{scale.x, scale.y, 0}) + offset_m: Mat4 = linalg.matrix4_translate(-Vec3{offset.x, offset.y, 0}) + shear_m: Mat4 = linalg.identity(Mat4) + shear_m[1,0] = shear.y + shear_m[0,1] = shear.x + + model := linalg.transpose(pos_m * rotation_m * scale_m * shear_m * offset_m) + + size := rect.size + + tl_pos := Vec4{0, 0, 0, 1} * model + tr_pos := Vec4{size.x, 0, 0, 1} * model + bl_pos := Vec4{0, size.y, 0, 1} * model + br_pos := Vec4{size.x, size.y, 0, 1} * model + + uv_start := rect.start / tex_size + uv_end := (rect.start + rect.size) / tex_size + + tl := Vertex{{tl_pos.x, tl_pos.y, depth}, {uv_start.x, uv_start.y}, color} + tr := Vertex{{tr_pos.x, tr_pos.y, depth}, {uv_end.x, uv_start.y}, color} + bl := Vertex{{bl_pos.x, bl_pos.y, depth}, {uv_start.x, uv_end.y}, color} + br := Vertex{{br_pos.x, br_pos.y, depth}, {uv_end.x, uv_end.y}, color} + + try_batch_calls(tex.handle, .Triangles, true) + + reserve(&renderer.draw_call.indices, len(renderer.draw_call.indices) + 6) + reserve(&renderer.draw_call.vertices, len(renderer.draw_call.vertices) + 4) + + start := Index(len(renderer.draw_call.vertices)) + + _add_vertex(tl) + _add_vertex(tr) + _add_vertex(bl) + _add_vertex(br) + + _add_index(start, 0) + _add_index(start, 1) + _add_index(start, 2) + _add_index(start, 2) + _add_index(start, 1) + _add_index(start, 3) +} diff --git a/src/fw/shader.frag b/src/fw/shader.frag new file mode 100644 index 0000000..fdc8796 --- /dev/null +++ b/src/fw/shader.frag @@ -0,0 +1,16 @@ +#version 430 core + +in vec2 f_uv; +in vec4 f_col; + +out vec4 final_color; + +uniform sampler2D tex0; + +void main() { + vec4 color = texture(tex0, f_uv) * f_col; + if (color.a <= 0.5) { + discard; + } + final_color = color; +} diff --git a/src/fw/shader.vert b/src/fw/shader.vert new file mode 100644 index 0000000..6bf4e24 --- /dev/null +++ b/src/fw/shader.vert @@ -0,0 +1,18 @@ +#version 430 core + +layout (location = 0) in vec3 v_pos; +layout (location = 1) in vec2 v_uv; +layout (location = 2) in vec4 v_col; + +out vec2 f_uv; +out vec4 f_col; + +uniform mat4 projection; +uniform mat4 view; + +void main() { + gl_Position = projection * view * vec4(v_pos, 1.0); + + f_uv = v_uv; + f_col = v_col; +} -- cgit v1.3-2-g0d8e