aboutsummaryrefslogtreecommitdiff
path: root/src/fw
diff options
context:
space:
mode:
authoriamcheeseman <[hidden email]>2026-02-03 22:25:00 -0500
committeriamcheeseman <[hidden email]>2026-02-03 22:25:00 -0500
commit3d1d31538d30a7f161f9f2b6d5e075ec69d3b860 (patch)
tree8b0deceb38c288dbef361bb4f77bb681b5566525 /src/fw
parent1c605da3ff8dc4295d2f9a85f5b7c8891ca84464 (diff)
ditch raylib (icky, and for loser BEGINNERS)
Diffstat (limited to 'src/fw')
-rw-r--r--src/fw/fw.odin114
-rw-r--r--src/fw/input.odin76
-rw-r--r--src/fw/keys.odin140
-rw-r--r--src/fw/opengl.odin363
-rw-r--r--src/fw/renderer.odin390
-rw-r--r--src/fw/shader.frag16
-rw-r--r--src/fw/shader.vert18
7 files changed, 1117 insertions, 0 deletions
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;
+}