diff options
Diffstat (limited to 'src/fw/renderer.odin')
| -rw-r--r-- | src/fw/renderer.odin | 390 |
1 files changed, 390 insertions, 0 deletions
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) +} |
