package fw import "core:log" import "core:image" import "core:math" import "core:math/linalg" WHITE :: Color{1, 1, 1, 1} BLACK :: Color{0, 0, 0, 1} TRANSPARENT :: Color{0, 0, 0, 0} RED :: Color{1, 0, 0, 1} GREEN :: Color{0, 1, 0, 1} BLUE :: Color{0, 0, 1, 1} Image :: image.Image 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, camera_pos: Vec2, draw_calls_count: i32, _draw_call_counter: i32, vt: VTable, } = {} @(private) _renderer_init :: proc(config: Config) { renderer.bg_col = BLACK 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) delete(renderer.draw_call.vertices) delete(renderer.draw_call.indices) switch renderer.backend { case .OpenGL: gl_deinit() } } set_camera_pos :: proc(pos: Vec2) { renderer.camera_pos = pos renderer.view = linalg.matrix4_translate( linalg.round(-Vec3{pos.x, pos.y, 0}), ) } @(require_results) get_camera_pos :: #force_inline proc() -> Vec2 { return renderer.camera_pos } 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) } @(require_results) 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) } } @(require_results) create_texture_from_image :: #force_inline proc(img: ^Image) -> Texture { return create_texture( img.pixels.buf[:], {i32(img.width), i32(img.height)}, i32(img.channels), ) } @(require_results) 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 := WHITE, ) { 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)) start := linalg.round(start) end := linalg.round(end) _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_gradient :: proc( pos: Vec2, size: Vec2, tl_color: Color, tr_color: Color, bl_color: Color, br_color: Color, depth: f32 = 0, 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) size := linalg.round(size) tl := Vertex{{pos.x, pos.y, depth}, {0, 0}, tl_color} tr := Vertex{{pos.x + size.x, pos.y, depth}, {1, 0}, tr_color} bl := Vertex{{pos.x, pos.y + size.y, depth}, {0, 1}, bl_color} br := Vertex{{pos.x + size.x, pos.y + size.y, depth}, {1, 1}, br_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_rect :: #force_inline proc( pos: Vec2, size: Vec2, depth: f32 = 0, color := WHITE, tex := renderer.white_1x1, lines := false, ) { draw_rect_gradient( pos, size, color, color, color, color, depth, tex, lines, ) } draw_tex :: proc( tex: Texture, pos: Vec2, depth: f32 = 0, scale := Vec2{1, 1}, offset := Vec2{0, 0}, color := WHITE, ) { draw_rect( pos = pos - (offset * scale), 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) rect := Rect{ start = linalg.round(rect.start), size = linalg.round(rect.size), } 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 := WHITE, ) { 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) } draw_progress_bar :: proc( pos: Vec2, size: Vec2, front_color: Color, back_color: Color, progress: f32, front_tex := renderer.white_1x1, back_tex := renderer.white_1x1, ) { draw_rect(pos, size, color = back_color, tex = back_tex) front_size := size front_size.x *= progress draw_rect(pos, front_size, color = front_color, tex = front_tex) }