From 797ab227e9822cef50ebc665f654d9df66c83f56 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 25 May 2024 11:52:23 -0400 Subject: [PATCH] Gut raylib usage from the codebase. Going to either fully commit to sokol or if it fails, rolling the platform layer myself. --- code/sectr/app/scratch.odin | 7 +- code/sectr/app/state.odin | 10 +- code/sectr/app/ui_theme.odin | 6 +- code/sectr/colors.odin | 189 ++++---- ...{client_api_sokol.odin => client_api.odin} | 28 +- code/sectr/engine/client_api_raylib.odin | 424 ------------------ .../engine/client_api_sokol_callbacks.odin | 7 +- code/sectr/engine/render_raylib.odin | 403 ----------------- code/sectr/engine/render_sokol.odin | 12 +- code/sectr/engine/render_text_raylib.odin | 226 ---------- code/sectr/engine/update.odin | 18 +- .../font/{provider_raylib.odin => cache.odin} | 2 +- code/sectr/font/fontstash.odin | 353 +++++++++++++++ code/sectr/font/provider.odin | 142 +----- .../provider_fontstash.odin} | 0 code/sectr/grime/context.odin | 23 +- code/sectr/grime/memory_tracker.odin | 26 +- code/sectr/input/input_sokol.odin | 1 + code/sectr/{engine => }/logger.odin | 15 +- code/sectr/parser/whitespace.odin | 6 +- code/sectr/project/manual_serialization.odin | 2 +- code/sectr/space.odin | 28 +- code/sectr/ui/core/layout.odin | 4 +- code/sectr/ui/core/state.odin | 1 + code/sectr/ui/core/style.odin | 6 +- 25 files changed, 571 insertions(+), 1368 deletions(-) rename code/sectr/engine/{client_api_sokol.odin => client_api.odin} (96%) delete mode 100644 code/sectr/engine/client_api_raylib.odin delete mode 100644 code/sectr/engine/render_raylib.odin delete mode 100644 code/sectr/engine/render_text_raylib.odin rename code/sectr/font/{provider_raylib.odin => cache.odin} (100%) create mode 100644 code/sectr/font/fontstash.odin rename code/sectr/{input/input_sdl.odin => font/provider_fontstash.odin} (100%) create mode 100644 code/sectr/input/input_sokol.odin rename code/sectr/{engine => }/logger.odin (87%) diff --git a/code/sectr/app/scratch.odin b/code/sectr/app/scratch.odin index ef1c914..2e5aac7 100644 --- a/code/sectr/app/scratch.odin +++ b/code/sectr/app/scratch.odin @@ -3,11 +3,10 @@ package sectr // Scratch space import sokol_gfx "thirdparty:sokol/gfx" -import rl "vendor:raylib" DebugData :: struct { square_size : i32, - square_pos : rl.Vector2, + square_pos : Vec2, debug_text_vis : b32, draw_debug_text_y : f32, @@ -35,10 +34,6 @@ DebugData :: struct { lorem_content : []byte, lorem_parse : PWS_ParseResult, - // Test 3d Viewport - cam_vp : rl.Camera3D, - viewport_rt : rl.RenderTexture, - gfx_clear_demo_pass_action : sokol_gfx.Pass_Action, gfx_tri_demo_state : struct { pipeline : sokol_gfx.Pipeline, diff --git a/code/sectr/app/state.odin b/code/sectr/app/state.odin index d516fd6..871c562 100644 --- a/code/sectr/app/state.odin +++ b/code/sectr/app/state.odin @@ -6,8 +6,6 @@ import "core:mem" import "core:mem/virtual" import "core:os" -import rl "vendor:raylib" - Str_App_State := "App State" #region("Memory") @@ -167,6 +165,7 @@ AppWindow :: struct { extent : Extents2, // Window half-size dpi_scale : f32, // Dots per inch scale (provided by raylib via glfw) ppcm : f32, // Dots per centimetre + resized : b32, // Extent changed this frame } FontData :: struct { @@ -241,9 +240,14 @@ State :: struct { font_rec_mono_semicasual_reg : FontID, default_font : FontID, + + // Context tracking + // These are used as implicit contextual states when doing immediate mode interfaces + // or for event callbacks that need their context assigned + // There are two potential UI contextes for this prototype so far, // the screen-space UI and the current workspace UI. - // This is used so that the ui api doesn't need to have the user pass the context every single time. + // This is used so that the ui api doesn't need to have the user pass the context through every proc. ui_context : ^UI_State, ui_floating_context : ^UI_FloatingManager, diff --git a/code/sectr/app/ui_theme.odin b/code/sectr/app/ui_theme.odin index ec6ae20..1a3e0f3 100644 --- a/code/sectr/app/ui_theme.odin +++ b/code/sectr/app/ui_theme.odin @@ -116,12 +116,12 @@ theme_table_row :: proc( is_even : bool ) -> UI_Theme if ! loaded { app_color := app_color_theme() - table_bg : Color + table_bg : RGBA8 if is_even { - table_bg = app_color.table_even_bg_color + table_bg = app_color.table_even_bg } else { - table_bg = app_color.table_odd_bg_color + table_bg = app_color.table_odd_bg } layout := UI_Layout { flags = {}, diff --git a/code/sectr/colors.odin b/code/sectr/colors.odin index ed39571..622e71b 100644 --- a/code/sectr/colors.odin +++ b/code/sectr/colors.odin @@ -1,32 +1,29 @@ package sectr -import rl "vendor:raylib" +RGBA8 :: struct { r, g, b, a : u8 } +Color_Blue :: RGBA8 { 90, 90, 230, 255 } +Color_Red :: RGBA8 { 230, 90, 90, 255 } +Color_White :: RGBA8 { 255, 255, 255, 255 } -Color :: rl.Color -Color_Blue :: rl.BLUE -// Color_Green :: rl.GREEN -Color_Red :: rl.RED -Color_White :: rl.WHITE +Color_Transparent :: RGBA8 { 0, 0, 0, 0 } +Color_BG :: RGBA8 { 55, 55, 60, 255 } +Color_BG_TextBox :: RGBA8 { 32, 32, 32, 180 } +Color_BG_Panel :: RGBA8 { 32, 32, 32, 255 } +Color_BG_Panel_Translucent :: RGBA8 { 32, 32, 32, 220 } +Color_BG_TextBox_Green :: RGBA8 { 102, 102, 110, 255 } +Color_Frame_Disabled :: RGBA8 { 22, 22, 22, 120 } +Color_Frame_Hover :: RGBA8 { 122, 122, 125, 200 } +Color_Frame_Select :: RGBA8 { 188, 188, 188, 220 } +Color_GreyRed :: RGBA8 { 220, 100, 100, 50 } +Color_White_A125 :: RGBA8 { 255, 255, 255, 165 } +Color_Black :: RGBA8 { 0, 0, 0, 255 } +Color_Green :: RGBA8 { 0, 180, 0, 255 } +Color_ResizeHandle :: RGBA8 { 80, 80, 90, 180 } -Color_Transparent :: Color { 0, 0, 0, 0 } -Color_BG :: Color { 55, 55, 60, 255 } -Color_BG_TextBox :: Color { 32, 32, 32, 180 } -Color_BG_Panel :: Color { 32, 32, 32, 255 } -Color_BG_Panel_Translucent :: Color { 32, 32, 32, 220 } -Color_BG_TextBox_Green :: Color { 102, 102, 110, 255 } -Color_Frame_Disabled :: Color { 22, 22, 22, 120 } -Color_Frame_Hover :: Color { 122, 122, 125, 200 } -Color_Frame_Select :: Color { 188, 188, 188, 220 } -Color_GreyRed :: Color { 220, 100, 100, 50 } -Color_White_A125 :: Color { 255, 255, 255, 165 } -Color_Black :: Color { 0, 0, 0, 255 } -Color_Green :: Color { 0, 180, 0, 255 } -Color_ResizeHandle :: Color { 80, 80, 90, 180 } +RGBA8_3D_BG :: RGBA8 { 188, 182 , 170, 255 } -Color_3D_BG :: Color { 188, 182 , 170, 255 } - -Color_Debug_UI_Padding_Bounds :: Color { 40, 195, 170, 160 } -Color_Debug_UI_Content_Bounds :: Color { 170, 120, 240, 160 } +RGBA8_Debug_UI_Padding_Bounds :: RGBA8 { 40, 195, 170, 160 } +RGBA8_Debug_UI_Content_Bounds :: RGBA8 { 170, 120, 240, 160 } // TODO(Ed): The entire rendering pass should be post-processed by a tone curve configurable for the user // This is how you properly support any tonality of light or dark themes and not have it be base don the monitors raw output. @@ -51,8 +48,8 @@ AppColorTheme :: struct { resize_hndl_hot, resize_hndl_active, - table_even_bg_color, - table_odd_bg_color, + table_even_bg, + table_odd_bg, text_default, text_hot, @@ -66,116 +63,116 @@ AppColorTheme :: struct { window_panel_bg, window_panel_border \ - : Color + : RGBA8 } App_Thm_Dark :: AppColorTheme { - light_limit = Color {185, 185, 185, 255}, - dark_limit = Color { 6, 6, 6, 255}, + light_limit = RGBA8 {185, 185, 185, 255}, + dark_limit = RGBA8 { 6, 6, 6, 255}, - bg = Color {16, 16, 16, 255}, + bg = RGBA8 {16, 16, 16, 255}, - border_default = Color { 54, 54, 54, 255}, + border_default = RGBA8 { 54, 54, 54, 255}, - btn_bg_default = Color { 32, 32, 32, 255}, - btn_bg_hot = Color { 80, 80, 100, 255}, - btn_bg_active = Color { 100, 130, 180, 255}, + btn_bg_default = RGBA8 { 32, 32, 32, 255}, + btn_bg_hot = RGBA8 { 80, 80, 100, 255}, + btn_bg_active = RGBA8 { 100, 130, 180, 255}, - input_box_bg = Color { 20, 20, 20, 255}, - input_box_bg_hot = Color { 25, 25, 25, 255}, - input_box_bg_active = Color { 15, 15, 15, 255}, + input_box_bg = RGBA8 { 20, 20, 20, 255}, + input_box_bg_hot = RGBA8 { 25, 25, 25, 255}, + input_box_bg_active = RGBA8 { 15, 15, 15, 255}, resize_hndl_default = Color_Transparent, - resize_hndl_hot = Color { 72, 72, 72, 90}, - resize_hndl_active = Color { 88, 88, 88, 90}, + resize_hndl_hot = RGBA8 { 72, 72, 72, 90}, + resize_hndl_active = RGBA8 { 88, 88, 88, 90}, - table_even_bg_color = Color { 35, 35, 35, 255}, - table_odd_bg_color = Color { 30, 30, 30, 255}, + table_even_bg = RGBA8 { 35, 35, 35, 255}, + table_odd_bg = RGBA8 { 30, 30, 30, 255}, - text_default = Color {140, 137, 135, 255}, - text_hot = Color {210, 210, 210, 255}, - text_active = Color {255, 255, 255, 255}, + text_default = RGBA8 {140, 137, 135, 255}, + text_hot = RGBA8 {210, 210, 210, 255}, + text_active = RGBA8 {255, 255, 255, 255}, - translucent_panel = Color { 30, 30, 30, 50}, + translucent_panel = RGBA8 { 30, 30, 30, 50}, - window_bar_border = Color{74, 74, 74, 255}, - window_bar_bg = Color{32, 32, 32, 255}, - window_btn_close_bg_hot = Color{65, 45, 45, 255}, + window_bar_border = RGBA8{74, 74, 74, 255}, + window_bar_bg = RGBA8{32, 32, 32, 255}, + window_btn_close_bg_hot = RGBA8{65, 45, 45, 255}, - window_panel_bg = Color{ 20, 20, 20, 50}, - window_panel_border = Color{ 84, 84, 84, 255}, + window_panel_bg = RGBA8{ 20, 20, 20, 50}, + window_panel_border = RGBA8{ 84, 84, 84, 255}, } App_Thm_Dusk :: AppColorTheme { - light_limit = Color {125, 125, 125, 255}, - dark_limit = Color { 10, 10, 10, 255}, + light_limit = RGBA8 {125, 125, 125, 255}, + dark_limit = RGBA8 { 10, 10, 10, 255}, - bg = Color {33, 33, 33, 255}, + bg = RGBA8 {33, 33, 33, 255}, - border_default = Color { 64, 64, 64, 255}, + border_default = RGBA8 { 64, 64, 64, 255}, - btn_bg_default = Color { 40, 40, 40, 255}, - btn_bg_hot = Color { 60, 60, 70, 255}, - btn_bg_active = Color { 90, 100, 130, 255}, + btn_bg_default = RGBA8 { 40, 40, 40, 255}, + btn_bg_hot = RGBA8 { 60, 60, 70, 255}, + btn_bg_active = RGBA8 { 90, 100, 130, 255}, - input_box_bg = Color { 20, 20, 20, 255}, - input_box_bg_hot = Color { 25, 25, 25, 255}, - input_box_bg_active = Color { 15, 15, 15, 255}, + input_box_bg = RGBA8 { 20, 20, 20, 255}, + input_box_bg_hot = RGBA8 { 25, 25, 25, 255}, + input_box_bg_active = RGBA8 { 15, 15, 15, 255}, resize_hndl_default = Color_Transparent, - resize_hndl_hot = Color { 72, 72, 72, 90}, - resize_hndl_active = Color { 88, 88, 88, 90}, + resize_hndl_hot = RGBA8 { 72, 72, 72, 90}, + resize_hndl_active = RGBA8 { 88, 88, 88, 90}, - table_even_bg_color = Color { 35, 35, 35, 255}, - table_odd_bg_color = Color { 30, 30, 30, 255}, + table_even_bg = RGBA8 { 35, 35, 35, 255}, + table_odd_bg = RGBA8 { 30, 30, 30, 255}, - text_default = Color {120, 117, 115, 255}, - text_hot = Color {180, 180, 180, 255}, - text_active = Color {240, 240, 240, 255}, + text_default = RGBA8 {120, 117, 115, 255}, + text_hot = RGBA8 {180, 180, 180, 255}, + text_active = RGBA8 {240, 240, 240, 255}, - translucent_panel = Color { 10, 10, 10, 50}, + translucent_panel = RGBA8 { 10, 10, 10, 50}, - window_bar_border = Color { 64, 64, 64, 255}, // border_default - window_bar_bg = Color{35, 35, 35, 255}, - window_btn_close_bg_hot = Color{45, 35, 35, 255}, + window_bar_border = RGBA8 { 64, 64, 64, 255}, // border_default + window_bar_bg = RGBA8{35, 35, 35, 255}, + window_btn_close_bg_hot = RGBA8{45, 35, 35, 255}, - window_panel_bg = Color { 10, 10, 10, 50}, // translucent_panel - window_panel_border = Color{24, 24, 24, 255}, + window_panel_bg = RGBA8 { 10, 10, 10, 50}, // translucent_panel + window_panel_border = RGBA8{24, 24, 24, 255}, } App_Thm_Light :: AppColorTheme { - light_limit = Color {195, 195, 195, 255}, - dark_limit = Color { 60, 60, 60, 255}, + light_limit = RGBA8 {195, 195, 195, 255}, + dark_limit = RGBA8 { 60, 60, 60, 255}, - bg = Color {135, 135, 135, 255}, + bg = RGBA8 {135, 135, 135, 255}, - border_default = Color { 174, 174, 174, 255}, + border_default = RGBA8 { 174, 174, 174, 255}, - btn_bg_default = Color { 160, 160, 160, 255}, - btn_bg_hot = Color { 145, 145, 155, 255}, - btn_bg_active = Color { 124, 124, 136, 255}, + btn_bg_default = RGBA8 { 160, 160, 160, 255}, + btn_bg_hot = RGBA8 { 145, 145, 155, 255}, + btn_bg_active = RGBA8 { 124, 124, 136, 255}, - input_box_bg = Color {115, 115, 115, 255}, - input_box_bg_hot = Color {125, 125, 125, 255}, - input_box_bg_active = Color {105, 105, 105, 255}, + input_box_bg = RGBA8 {115, 115, 115, 255}, + input_box_bg_hot = RGBA8 {125, 125, 125, 255}, + input_box_bg_active = RGBA8 {105, 105, 105, 255}, resize_hndl_default = Color_Transparent, - resize_hndl_hot = Color { 95, 95, 95, 90}, - resize_hndl_active = Color { 80, 80, 80, 90}, + resize_hndl_hot = RGBA8 { 95, 95, 95, 90}, + resize_hndl_active = RGBA8 { 80, 80, 80, 90}, - table_even_bg_color = Color {150, 150, 150, 255}, - table_odd_bg_color = Color {160, 160, 160, 255}, + table_even_bg = RGBA8 {150, 150, 150, 255}, + table_odd_bg = RGBA8 {160, 160, 160, 255}, - text_default = Color { 55, 55, 55, 255}, - text_hot = Color { 85, 85, 85, 255}, - text_active = Color { 45, 45, 49, 255}, + text_default = RGBA8 { 55, 55, 55, 255}, + text_hot = RGBA8 { 85, 85, 85, 255}, + text_active = RGBA8 { 45, 45, 49, 255}, - translucent_panel = Color { 110, 110, 110, 50}, + translucent_panel = RGBA8 { 110, 110, 110, 50}, - window_bar_border = Color{ 174, 174, 174, 255}, // border_default - window_bar_bg = Color{ 155, 155, 155, 255}, - window_btn_close_bg_hot = Color{ 145, 135, 135, 255}, + window_bar_border = RGBA8{ 174, 174, 174, 255}, // border_default + window_bar_bg = RGBA8{ 155, 155, 155, 255}, + window_btn_close_bg_hot = RGBA8{ 145, 135, 135, 255}, - window_panel_bg = Color {135, 135, 135, 50}, // translucent_panel - window_panel_border = Color{184, 184, 184, 255}, + window_panel_bg = RGBA8 {135, 135, 135, 50}, // translucent_panel + window_panel_border = RGBA8{184, 184, 184, 255}, } diff --git a/code/sectr/engine/client_api_sokol.odin b/code/sectr/engine/client_api.odin similarity index 96% rename from code/sectr/engine/client_api_sokol.odin rename to code/sectr/engine/client_api.odin index c98dc6e..7a50452 100644 --- a/code/sectr/engine/client_api_sokol.odin +++ b/code/sectr/engine/client_api.odin @@ -15,8 +15,6 @@ import sokol_app "thirdparty:sokol/app" import sokol_gfx "thirdparty:sokol/gfx" import sokol_app_gfx_glue "thirdparty:sokol/glue" -import rl "vendor:raylib" - Path_Assets :: "../assets/" Path_Shaders :: "../shaders/" Path_Input_Replay :: "scratch.sectr_replay" @@ -104,15 +102,17 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem } // Setup input frame poll references - input = & input_data[1] - input_prev = & input_data[0] - for & input in input_data { - using input - error : AllocatorError - keyboard_events.keys_pressed, error = array_init_reserve(KeyCode, persistent_slab_allocator(), Kilo) - ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.keys_pressed array") - keyboard_events.chars_pressed, error = array_init_reserve(rune, persistent_slab_allocator(), Kilo) - ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.chars_pressed array") + { + input = & input_data[1] + input_prev = & input_data[0] + for & input in input_data { + using input + error : AllocatorError + keyboard_events.keys_pressed, error = array_init_reserve(KeyCode, persistent_slab_allocator(), Kilo) + ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.keys_pressed array") + keyboard_events.chars_pressed, error = array_init_reserve(rune, persistent_slab_allocator(), Kilo) + ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.chars_pressed array") + } } // Configuration Load @@ -305,9 +305,9 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem { using project.workspace cam = { - target = { 0, 0 }, - offset = transmute(Vec2) app_window.extent, - rotation = 0, + position = { 0, 0 }, + view = transmute(Vec2) app_window.extent, + // rotation = 0, zoom = 1.0, } // cam = { diff --git a/code/sectr/engine/client_api_raylib.odin b/code/sectr/engine/client_api_raylib.odin deleted file mode 100644 index 0b390d9..0000000 --- a/code/sectr/engine/client_api_raylib.odin +++ /dev/null @@ -1,424 +0,0 @@ -package sectr - -import "base:runtime" -import c "core:c/libc" -import "core:dynlib" -import "core:mem" -import "core:mem/virtual" -import "core:os" -import "core:slice" -import "core:strings" -import "core:time" -import "core:prof/spall" -import rl "vendor:raylib" - -when false { -Path_Assets :: "../assets/" -Path_Shaders :: "../shaders/" -Path_Input_Replay :: "scratch.sectr_replay" - -Persistent_Slab_DBG_Name := "Peristent Slab" -Frame_Slab_DBG_Name := "Frame Slab" -Transient_Slab_DBG_Name := "Transient Slab" - -ModuleAPI :: struct { - lib : dynlib.Library, - write_time : FileTime, - lib_version : i32, - - startup : type_of( startup ), - shutdown : type_of( sectr_shutdown ), - reload : type_of( reload ), - tick : type_of( tick ), - clean_frame : type_of( clean_frame ), -} - -@export -startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^Logger ) -{ - spall.SCOPED_EVENT( & prof.ctx, & prof.buffer, #procedure ) - Memory_App.profiler = prof - - startup_tick := time.tick_now() - - logger_init( & Memory_App.logger, "Sectr", host_logger.file_path, host_logger.file ) - context.logger = to_odin_logger( & Memory_App.logger ) - - // Setup memory for the first time - { - using Memory_App; - persistent = persistent_mem - frame = frame_mem - transient = transient_mem - files_buffer = files_buffer_mem - - context.allocator = transient_allocator() - context.temp_allocator = transient_allocator() - // TODO(Ed) : Put on the transient allocator a slab allocator (transient slab) - } - - state := new( State, persistent_allocator() ) - Memory_App.state = state - using state - - // Setup Persistent Slabs & String Cache - { - alignment := uint(mem.DEFAULT_ALIGNMENT) - - policy_ptr := & default_slab_policy - push( policy_ptr, SlabSizeClass { 128 * Kilobyte, 1 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 256 * Kilobyte, 2 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 512 * Kilobyte, 4 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 1 * Megabyte, 16 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 1 * Megabyte, 32 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 1 * Megabyte, 64 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 2 * Megabyte, 128 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 2 * Megabyte, 256 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 2 * Megabyte, 512 * Kilobyte, alignment }) - push( policy_ptr, SlabSizeClass { 2 * Megabyte, 1 * Megabyte, alignment }) - push( policy_ptr, SlabSizeClass { 2 * Megabyte, 2 * Megabyte, alignment }) - push( policy_ptr, SlabSizeClass { 4 * Megabyte, 4 * Megabyte, alignment }) - push( policy_ptr, SlabSizeClass { 8 * Megabyte, 8 * Megabyte, alignment }) - push( policy_ptr, SlabSizeClass { 16 * Megabyte, 16 * Megabyte, alignment }) - push( policy_ptr, SlabSizeClass { 32 * Megabyte, 32 * Megabyte, alignment }) - push( policy_ptr, SlabSizeClass { 64 * Megabyte, 64 * Megabyte, alignment }) - // push( policy_ptr, SlabSizeClass { 128 * Megabyte, 128 * Megabyte, alignment }) - // push( policy_ptr, SlabSizeClass { 256 * Megabyte, 256 * Megabyte, alignment }) - // push( policy_ptr, SlabSizeClass { 512 * Megabyte, 512 * Megabyte, alignment }) - - alloc_error : AllocatorError - persistent_slab, alloc_error = slab_init( policy_ptr, allocator = persistent_allocator(), dbg_name = Persistent_Slab_DBG_Name ) - verify( alloc_error == .None, "Failed to allocate the persistent slab" ) - - transient_slab, alloc_error = slab_init( & default_slab_policy, allocator = transient_allocator(), dbg_name = Transient_Slab_DBG_Name ) - verify( alloc_error == .None, "Failed to allocate transient slab" ) - - transient_clear_time = 120 // Seconds, 2 Minutes - - string_cache = str_cache_init() - } - - // Setup input frame poll references - input = & input_data[1] - input_prev = & input_data[0] - for & input in input_data { - using input - error : AllocatorError - keyboard_events.keys_pressed, error = array_init_reserve(KeyCode, persistent_slab_allocator(), Kilo) - ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.keys_pressed array") - keyboard_events.chars_pressed, error = array_init_reserve(rune, persistent_slab_allocator(), Kilo) - ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.chars_pressed array") - } - - // Configuration Load - // TODO(Ed): Make this actually load from an ini - { - using config - resolution_width = 1000 - resolution_height = 600 - refresh_rate = 0 - - cam_min_zoom = 0.10 - cam_max_zoom = 30.0 - cam_zoom_mode = .Smooth - cam_zoom_smooth_snappiness = 4.0 - cam_zoom_sensitivity_digital = 0.2 - cam_zoom_sensitivity_smooth = 4.0 - - engine_refresh_hz = 0 - - timing_fps_moving_avg_alpha = 0.9 - - ui_resize_border_width = 5 - - color_theme = App_Thm_Dusk - } - - Desired_OS_Scheduler_MS :: 1 - sleep_is_granular = set__scheduler_granularity( Desired_OS_Scheduler_MS ) - - // Rough setup of window with rl stuff - { - // rl.Odin_SetMalloc( RL_MALLOC ) - - rl.SetConfigFlags( { - rl.ConfigFlag.WINDOW_RESIZABLE, - rl.ConfigFlag.WINDOW_TOPMOST, - }) - - window_width : i32 = cast(i32) config.resolution_width - window_height : i32 = cast(i32) config.resolution_height - win_title : cstring = "Sectr Prototype" - rl.InitWindow( window_width, window_height, win_title ) - log( "Raylib initialized and window opened" ) - - window := & state.app_window - window.extent.x = f32(window_width) * 0.5 - window.extent.y = f32(window_height) * 0.5 - - // We do not support non-uniform DPI. - window.dpi_scale = rl.GetWindowScaleDPI().x - window.ppcm = os_default_ppcm * window.dpi_scale - - // Determining current monitor and setting the target frametime based on it.. - monitor_id = rl.GetCurrentMonitor() - monitor_refresh_hz = rl.GetMonitorRefreshRate( monitor_id ) - - if config.engine_refresh_hz == 0 { - config.engine_refresh_hz = uint(monitor_refresh_hz) - } - } - - // Basic Font Setup - { - font_provider_startup() - // path_rec_mono_semicasual_reg := strings.concatenate( { Path_Assets, "RecMonoSemicasual-Regular-1.084.ttf" }) - // font_rec_mono_semicasual_reg = font_load( path_rec_mono_semicasual_reg, 24.0, "RecMonoSemiCasual_Regular" ) - - // path_squidgy_slimes := strings.concatenate( { Path_Assets, "Squidgy Slimes.ttf" } ) - // font_squidgy_slimes = font_load( path_squidgy_slimes, 24.0, "Squidgy_Slime" ) - - path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" }, transient_allocator() ) - font_firacode = font_load( path_firacode, 24.0, "FiraCode" ) - default_font = font_firacode - log( "Default font loaded" ) - } - - // Setup the screen ui state - { - ui_startup( & screen_ui.base, cache_allocator = persistent_slab_allocator() ) - ui_floating_startup( & screen_ui.floating, persistent_slab_allocator(), 1 * Kilobyte, 1 * Kilobyte, "screen ui floating manager" ) - - using screen_ui - menu_bar.pos = { -60, 0 } - // menu_bar.pos = Vec2(app_window.extent) * { -1, 1 } - menu_bar.size = {140, 40} - - settings_menu.min_size = {250, 200} - } - - // Demo project setup - // TODO(Ed): This will eventually have to occur when the user either creates or loads a workspace. I don't know - { - using project - path = str_intern("./") - name = str_intern( "First Project" ) - workspace.name = str_intern( "First Workspace" ) - { - using project.workspace - cam = { - target = { 0, 0 }, - offset = transmute(Vec2) app_window.extent, - rotation = 0, - zoom = 1.0, - } - // cam = { - // position = { 0, 0, -100 }, - // target = { 0, 0, 0 }, - // up = { 0, 1, 0 }, - // fovy = 90, - // projection = rl.CameraProjection.ORTHOGRAPHIC, - // } - - // Setup workspace UI state - ui_startup( & workspace.ui, cache_allocator = persistent_slab_allocator() ) - } - - debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum.txt", allocator = persistent_slab_allocator()) - - alloc_error : AllocatorError; success : bool - debug.lorem_content, success = os.read_entire_file( debug.path_lorem, persistent_slab_allocator() ) - - debug.lorem_parse, alloc_error = pws_parser_parse( transmute(string) debug.lorem_content, persistent_slab_allocator() ) - verify( alloc_error == .None, "Faield to parse due to allocation failure" ) - - // Render texture test - // debug.viewport_rt = rl.LoadRenderTexture( 1280, 720 ) - - // debug.proto_text_shader = rl.LoadShader( "C:/projects/SectrPrototype/code/shaders/text_shader.vs", "C:/projects/SectrPrototype/code/shaders/text_shader.fs" ) - } - - startup_ms := duration_ms( time.tick_lap_time( & startup_tick)) - log( str_fmt_tmp("Startup time: %v ms", startup_ms) ) - - // Make sure to cleanup transient before continuing... - // From here on, tarnsinet usage has to be done with care. - // For most cases, the frame allocator should be more than enough. -} - -// For some reason odin's symbols conflict with native foreign symbols... -@export -sectr_shutdown :: proc() -{ - context.logger = to_odin_logger( & Memory_App.logger ) - - if Memory_App.persistent == nil { - return - } - state := get_state() - - // Replay - { - file_close( Memory_App.replay.active_file ) - } - - font_provider_shutdown() - - log("Module shutdown complete") -} - -@export -reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger ) -{ - spall.SCOPED_EVENT( & prof.ctx, & prof.buffer, #procedure ) - Memory_App.profiler = prof - - context.logger = to_odin_logger( & Memory_App.logger ) - using Memory_App; - - persistent = persistent_mem - frame = frame_mem - transient = transient_mem - files_buffer = files_buffer_mem - - context.allocator = persistent_allocator() - context.temp_allocator = transient_allocator() - - Memory_App.state = get_state() - using state - - // Procedure Addresses are not preserved on hot-reload. They must be restored for persistent data. - // The only way to alleviate this is to either do custom handles to allocators - // Or as done below, correct containers using allocators on reload. - // Thankfully persistent dynamic allocations are rare, and thus we know exactly which ones they are. - - slab_reload( persistent_slab, persistent_allocator() ) - - hmap_chained_reload( font_provider_data.font_cache, persistent_allocator()) - - slab_reload( string_cache.slab, persistent_allocator() ) - zpl_hmap_reload( & string_cache.table, persistent_slab_allocator()) - - slab_reload( frame_slab, frame_allocator()) - slab_reload( transient_slab, transient_allocator()) - - ui_reload( & get_state().project.workspace.ui, cache_allocator = persistent_slab_allocator() ) - - log("Module reloaded") -} - -@export -tick :: proc( host_delta_time : f64, host_delta_ns : Duration ) -> b32 -{ - profile( "Client Tick" ) - context.logger = to_odin_logger( & Memory_App.logger ) - state := get_state(); using state - - should_close : b32 - - client_tick := time.tick_now() - { - profile("Work frame") - - // Setup Frame Slab - { - alloc_error : AllocatorError - frame_slab, alloc_error = slab_init( & default_slab_policy, bucket_reserve_num = 0, - allocator = frame_allocator(), - dbg_name = Frame_Slab_DBG_Name, - should_zero_buckets = true ) - verify( alloc_error == .None, "Failed to allocate frame slab" ) - } - - context.allocator = frame_slab_allocator() - context.temp_allocator = transient_allocator() - - rl.PollInputEvents() - - debug.draw_ui_box_bounds_points = false - debug.draw_UI_padding_bounds = false - debug.draw_ui_content_bounds = false - - // config.color_theme = App_Thm_Light - // config.color_theme = App_Thm_Dusk - config.color_theme = App_Thm_Dark - - should_close = update( host_delta_time ) - render() - - rl.SwapScreenBuffer() - } - - // Timing - { - // profile("Client tick timing processing") - // config.engine_refresh_hz = uint(monitor_refresh_hz) - // config.engine_refresh_hz = 6 - frametime_target_ms = 1.0 / f64(config.engine_refresh_hz) * S_To_MS - sub_ms_granularity_required := frametime_target_ms <= Frametime_High_Perf_Threshold_MS - - frametime_delta_ns = time.tick_lap_time( & client_tick ) - frametime_delta_ms = duration_ms( frametime_delta_ns ) - frametime_delta_seconds = duration_seconds( frametime_delta_ns ) - frametime_elapsed_ms = frametime_delta_ms + host_delta_time - - if frametime_elapsed_ms < frametime_target_ms - { - sleep_ms := frametime_target_ms - frametime_elapsed_ms - pre_sleep_tick := time.tick_now() - - if sleep_ms > 0 { - thread_sleep( cast(Duration) sleep_ms * MS_To_NS ) - // thread__highres_wait( sleep_ms ) - } - - sleep_delta_ns := time.tick_lap_time( & pre_sleep_tick) - sleep_delta_ms := duration_ms( sleep_delta_ns ) - - if sleep_delta_ms < sleep_ms { - // log( str_fmt_tmp("frametime sleep was off by: %v ms", sleep_delta_ms - sleep_ms )) - } - - frametime_elapsed_ms += sleep_delta_ms - for ; frametime_elapsed_ms < frametime_target_ms; { - sleep_delta_ns = time.tick_lap_time( & pre_sleep_tick) - sleep_delta_ms = duration_ms( sleep_delta_ns ) - - frametime_elapsed_ms += sleep_delta_ms - } - } - - config.timing_fps_moving_avg_alpha = 0.99 - frametime_avg_ms = mov_avg_exp( f64(config.timing_fps_moving_avg_alpha), frametime_elapsed_ms, frametime_avg_ms ) - fps_avg = 1 / (frametime_avg_ms * MS_To_S) - - if frametime_elapsed_ms > 60.0 { - context.allocator = transient_allocator() - log( str_fmt("Big tick! %v ms", frametime_elapsed_ms), LogLevel.Warning ) - } - } - return should_close -} - -@export -clean_frame :: proc() -{ - // profile( #procedure) - state := get_state(); using state - context.logger = to_odin_logger( & Memory_App.logger ) - - free_all( frame_allocator() ) - - transient_clear_elapsed += frametime_delta32() - if transient_clear_elapsed >= transient_clear_time && ! transinet_clear_lock - { - transient_clear_elapsed = 0 - free_all( transient_allocator() ) - - alloc_error : AllocatorError - transient_slab, alloc_error = slab_init( & default_slab_policy, allocator = transient_allocator(), dbg_name = Transient_Slab_DBG_Name ) - verify( alloc_error == .None, "Failed to allocate transient slab" ) - } -} - -} // when false \ No newline at end of file diff --git a/code/sectr/engine/client_api_sokol_callbacks.odin b/code/sectr/engine/client_api_sokol_callbacks.odin index d946d8d..4d6142e 100644 --- a/code/sectr/engine/client_api_sokol_callbacks.odin +++ b/code/sectr/engine/client_api_sokol_callbacks.odin @@ -26,8 +26,9 @@ sokol_app_frame_callback :: proc "c" () { window := & state.app_window if int(window.extent.x) != int(sokol_width) || int(window.extent.y) != int(sokol_height) { - window.extent.x = sokol_width - window.extent.y = sokol_height + window.resized = true + window.extent.x = sokol_width * 0.5 + window.extent.y = sokol_height * 0.5 log("sokol_app: Event-based frame callback triggered (detected a resize") } @@ -38,6 +39,8 @@ sokol_app_frame_callback :: proc "c" () { client_tick := time.tick_now() should_close |= tick_work_frame( sokol_delta_ms ) tick_frametime( & client_tick, sokol_delta_ms, sokol_delta_ns ) + + window.resized = false } sokol_app_cleanup_callback :: proc "c" () { diff --git a/code/sectr/engine/render_raylib.odin b/code/sectr/engine/render_raylib.odin deleted file mode 100644 index 2a6e9b9..0000000 --- a/code/sectr/engine/render_raylib.odin +++ /dev/null @@ -1,403 +0,0 @@ -package sectr - -import "core:fmt" - -import rl "vendor:raylib" - -when false { -range2_to_rl_rect :: #force_inline proc "contextless"( range : Range2 ) -> rl.Rectangle -{ - rect := rl.Rectangle { - range.min.x, - range.max.y, - abs(range.max.x - range.min.x), - abs(range.max.y - range.min.y), - } - return rect -} - -draw_rectangle :: #force_inline proc "contextless" ( rect : rl.Rectangle, box : ^UI_RenderBoxInfo ) { - using box - if style.corner_radii[0] > 0 { - rl.DrawRectangleRounded( rect, style.corner_radii[0], 9, style.bg_color ) - } - else { - rl.DrawRectangleRec( rect, style.bg_color ) - } -} - -draw_rectangle_lines :: #force_inline proc "contextless" ( rect : rl.Rectangle, box : ^UI_RenderBoxInfo, color : Color, thickness : f32 ) { - using box - if style.corner_radii[0] > 0 { - rl.DrawRectangleRoundedLines( rect, style.corner_radii[0], 9, thickness, color ) - } - else { - rl.DrawRectangleLinesEx( rect, thickness, color ) - } -} - -render :: proc() -{ - profile(#procedure) - state := get_state(); using state - - render_mode_3d() - - rl.BeginDrawing() - rl.ClearBackground( app_color_theme().bg ) - - render_mode_2d_workspace() - render_mode_screenspace() - - rl.EndDrawing() -} - -// Experimental 3d viewport, not really the focus of this prototype -// Until we can have a native or interpreted program render to it its not very useful. -// Note(Ed): Other usecase could be 3d vis notes & math/graphical debug -render_mode_3d :: proc() -{ - profile(#procedure) - - state := get_state(); using state - - rl.BeginDrawing() - rl.BeginTextureMode( debug.viewport_rt ) - rl.BeginMode3D( debug.cam_vp ) - rl.ClearBackground( Color_3D_BG ) - - rl.EndMode3D() - rl.EndTextureMode() - rl.EndDrawing() -} - -// TODO(Ed): Eventually this needs to become a 'viewport within a UI' -// This would allow the user to have more than one workspace open at the same time -render_mode_2d_workspace :: proc() -{ - profile(#procedure) - state := get_state(); using state - cam := & project.workspace.cam - - win_extent := state.app_window.extent - - rl.BeginMode2D( project.workspace.cam ) - - // Draw 3D Viewport - when false - { - viewport_size := Vec2 { 1280.0, 720.0 } - vp_half_size := viewport_size * 0.5 - viewport_box := range2( -vp_half_size, vp_half_size ) - viewport_render := range2( - ws_view_to_render_pos( viewport_box.min), - ws_view_to_render_pos( viewport_box.max), - ) - viewport_rect := range2_to_rl_rect( viewport_render ) - rl.DrawTextureRec( debug.viewport_rt.texture, viewport_rect, -vp_half_size, Color_White ) - } - - // draw_text( "This is text in world space", { 0, 200 }, 16.0 ) - - cam_zoom_ratio := 1.0 / cam.zoom - - view_bounds := view_get_bounds() - when false - { - render_view := Range2 { pts = { - ws_view_to_render_pos( view_bounds.min), - ws_view_to_render_pos( view_bounds.max), - }} - view_rect := rl.Rectangle { - render_view.min.x, - render_view.max.y, - abs(render_view.max.x - render_view.min.x), - abs(render_view.max.y - render_view.min.y), - } - rl.DrawRectangleRounded( view_rect, 0.3, 9, { 255, 0, 0, 20 } ) - } - - ImguiRender: - { - profile("Imgui Render") - ui := & state.project.workspace.ui - root := ui.root - if root == nil || root.num_children == 0 { - break ImguiRender - } - state.ui_context = ui - - current := root.first - for & current in array_to_slice(ui.render_queue) - { - profile("Box") - style := current.style - computed := current.computed - - if ! intersects_range2( view_bounds, computed.bounds ) { - continue - } - - profile_begin("render space calc") - render_bounds := range2( - ws_view_to_render_pos(computed.bounds.min), - ws_view_to_render_pos(computed.bounds.max), - ) - render_padding := range2( - ws_view_to_render_pos(computed.padding.min), - ws_view_to_render_pos(computed.padding.max), - ) - render_content := range2( - ws_view_to_render_pos(computed.content.min), - ws_view_to_render_pos(computed.content.max), - ) - - rect_bounds := range2_to_rl_rect( render_bounds ) - rect_padding := range2_to_rl_rect( render_padding ) - rect_content := range2_to_rl_rect( render_content ) - profile_end() - - profile_begin("raylib drawing") - if style.bg_color.a != 0 - { - draw_rectangle( rect_bounds, & current ) - } - if current.border_width > 0 { - draw_rectangle_lines( rect_bounds, & current, style.border_color, current.border_width ) - } - - line_thickness := 1 * cam_zoom_ratio - if debug.draw_UI_padding_bounds && equal_range2(computed.content, computed.padding) { - draw_rectangle_lines( rect_padding, & current, Color_Debug_UI_Padding_Bounds, line_thickness ) - } - else if debug.draw_ui_content_bounds { - draw_rectangle_lines( rect_content, & current, Color_Debug_UI_Content_Bounds, line_thickness ) - } - - point_radius := 3 * cam_zoom_ratio - - // profile_begin("circles") - if debug.draw_ui_box_bounds_points - { - computed_size := computed.bounds.p1 - computed.bounds.p0 - // center := Vec2 { - // render_bounds.p0.x + computed_size.x * 0.5, - // render_bounds.p0.y - computed_size.y * 0.5, - // } - // rl.DrawCircleV( center, point_radius, Color_White ) - - rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red ) - rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) - } - // profile_end() - - profile_end() - - if len(current.text.str) > 0 { - ws_view_draw_text( current.text, ws_view_to_render_pos(computed.text_pos * {1, -1}), current.font_size, style.text_color ) - } - } - } - //endregion Imgui Render - - - if debug.mouse_vis { - cursor_world_pos := screen_to_ws_view_pos(input.mouse.pos) - rl.DrawCircleV( ws_view_to_render_pos(cursor_world_pos), 5, Color_GreyRed ) - } - - rl.DrawCircleV( { 0, 0 }, 1 * cam_zoom_ratio, Color_White ) - - rl.EndMode2D() -} - -render_mode_screenspace :: proc () -{ - profile("Render Screenspace") - - state := get_state(); using state - replay := & Memory_App.replay - cam := & project.workspace.cam - win_extent := state.app_window.extent - - render_screen_ui() - - debug_text :: proc( format : string, args : ..any ) - { - @static draw_text_scratch : [Kilobyte * 64]u8 - - state := get_state(); using state - if debug.draw_debug_text_y > 800 { - debug.draw_debug_text_y = 0 - } - - cam := & project.workspace.cam - screen_corners := screen_get_corners() - - position := screen_corners.top_right - position.x -= app_window.extent.x - position.y -= debug.draw_debug_text_y - - content := str_fmt_buffer( draw_text_scratch[:], format, ..args ) - debug_draw_text( content, position, 12.0 ) - - debug.draw_debug_text_y += 14 - } - - if debug.debug_text_vis - { - fps_msg := str_fmt( "FPS: %f", fps_avg) - fps_msg_width := measure_text_size( fps_msg, default_font, 12.0, 0.0 ).x - fps_msg_pos := screen_get_corners().top_right - { fps_msg_width, 0 } - { 5, 5 } - debug_draw_text( fps_msg, fps_msg_pos, 12.0, color = rl.GREEN ) - - // debug_text( "Screen Width : %v", rl.GetScreenWidth () ) - // debug_text( "Screen Height: %v", rl.GetScreenHeight() ) - // debug_text( "frametime_target_ms : %f ms", frametime_target_ms ) - debug_text( "frametime : %f ms", frametime_delta_ms ) - // debug_text( "frametime_last_elapsed_ms : %f ms", frametime_elapsed_ms ) - if replay.mode == ReplayMode.Record { - debug_text( "Recording Input") - } - if replay.mode == ReplayMode.Playback { - debug_text( "Replaying Input") - } - // debug_text("Zoom Target: %v", project.workspace.zoom_target) - - if debug.mouse_vis { - debug_text("Mouse Vertical Wheel: %v", input.mouse.vertical_wheel ) - debug_text("Mouse Delta : %v", input.mouse.delta ) - debug_text("Mouse Position (Render) : %v", input.mouse.raw_pos ) - debug_text("Mouse Position (Screen) : %v", input.mouse.pos ) - debug_text("Mouse Position (Workspace View): %v", screen_to_ws_view_pos(input.mouse.pos) ) - rl.DrawCircleV( input.mouse.raw_pos, 10, Color_White_A125 ) - rl.DrawCircleV( screen_to_render_pos(input.mouse.pos), 2, Color_BG ) - } - - ui := & project.workspace.ui - - if true - { - debug_text("Box Count (Workspace): %v", ui.built_box_count ) - - hot_box := ui_box_from_key( ui.curr_cache, ui.hot ) - active_box := ui_box_from_key( ui.curr_cache, ui.active ) - if hot_box != nil { - debug_text("Worksapce Hot Box : %v", hot_box.label.str ) - debug_text("Workspace Hot Range2: %v", hot_box.computed.bounds.pts) - } - if active_box != nil{ - debug_text("Workspace Active Box: %v", active_box.label.str ) - } - } - - ui = & screen_ui - - if true - { - debug_text("Box Count: %v", ui.built_box_count ) - - hot_box := ui_box_from_key( ui.curr_cache, ui.hot ) - active_box := ui_box_from_key( ui.curr_cache, ui.active ) - if hot_box != nil { - debug_text("Hot Box : %v", hot_box.label.str ) - debug_text("Hot Range2: %v", hot_box.computed.bounds.pts) - } - if active_box != nil{ - debug_text("Active Box: %v", active_box.label.str ) - } - } - - debug.draw_debug_text_y = 14 - } -} - -// A non-zoomable static-view for ui -// Only a scalar factor may be applied to the size of widgets & fonts -// 'Window tiled' panels reside here -render_screen_ui :: proc() -{ - profile(#procedure) - - using state := get_state() - - //region App UI - Render_App_UI: - { - profile("App UI") - ui := & state.screen_ui - state.ui_context = ui - root := ui.root - if root.num_children == 0 { - break Render_App_UI - } - - for & current in array_to_slice(ui.render_queue) - { - profile("Box") - - style := current.style - computed := & current.computed - - profile_begin("Coordinate space conversion") - render_bounds := range2( - screen_to_render_pos(computed.bounds.min), - screen_to_render_pos(computed.bounds.max), - ) - render_padding := range2( - screen_to_render_pos(computed.padding.min), - screen_to_render_pos(computed.padding.max), - ) - render_content := range2( - screen_to_render_pos(computed.content.min), - screen_to_render_pos(computed.content.max), - ) - rect_bounds := range2_to_rl_rect( render_bounds ) - rect_padding := range2_to_rl_rect( render_padding ) - rect_content := range2_to_rl_rect( render_content ) - profile_end() - - profile_begin("raylib drawing") - if style.bg_color.a != 0 - { - draw_rectangle( rect_bounds, & current ) - } - if current.border_width > 0 { - draw_rectangle_lines( rect_bounds, & current, style.border_color, current.border_width ) - } - - line_thickness : f32 = 1 - - if debug.draw_UI_padding_bounds && equal_range2(computed.content, computed.padding) { - draw_rectangle_lines( rect_padding, & current, Color_Debug_UI_Padding_Bounds, line_thickness ) - } - else if debug.draw_ui_content_bounds { - draw_rectangle_lines( rect_content, & current, Color_Debug_UI_Content_Bounds, line_thickness ) - } - - point_radius : f32 = 3 - - if debug.draw_ui_box_bounds_points - { - computed_size := computed.bounds.p1 - computed.bounds.p0 - if false - { - center := Vec2 { - render_bounds.p0.x + computed_size.x * 0.5, - render_bounds.p0.y - computed_size.y * 0.5, - } - rl.DrawCircleV( center, point_radius, Color_White ) - } - rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red ) - rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) - } - - if len(current.text.str) > 0 && style.font.key != 0 { - draw_text_screenspace( current.text, screen_to_render_pos(computed.text_pos), current.font_size, style.text_color ) - } - profile_end() - } - } - //endregion App UI -} -} // when false diff --git a/code/sectr/engine/render_sokol.odin b/code/sectr/engine/render_sokol.odin index 507d466..da3b4a5 100644 --- a/code/sectr/engine/render_sokol.odin +++ b/code/sectr/engine/render_sokol.odin @@ -22,7 +22,7 @@ render :: proc() } // Triangle Demo - if true + if false { using debug.gfx_tri_demo_state sokol_gfx.begin_pass(sokol_gfx.Pass { action = pass_action, swapchain = sokol_glue.swapchain() }) @@ -34,4 +34,14 @@ render :: proc() sokol_gfx.end_pass() sokol_gfx.commit() } + + + // Batching Enqueue Boxes + // Mixed with the batching enqueue for text + + + //Begin + // Flush boxs + // flush text + // End } diff --git a/code/sectr/engine/render_text_raylib.odin b/code/sectr/engine/render_text_raylib.odin deleted file mode 100644 index ed2f6dc..0000000 --- a/code/sectr/engine/render_text_raylib.odin +++ /dev/null @@ -1,226 +0,0 @@ -package sectr - -import "core:math" -import "core:strings" -import "core:unicode/utf8" -import rl "vendor:raylib" - -when false { -debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) -{ - // profile(#procedure) - state := get_state(); using state - - if len( content ) == 0 { - return - } - runes, alloc_error := to_runes( content, frame_allocator() ) - // runes, alloc_error := to_runes( content, context.temp_allocator ) - // verify( alloc_error == AllocatorError.None, "Failed to temp allocate runes" ) - - font := font - if font.key == Font_Default.key { - // if ( len(font) == 0 ) { - font = default_font - } - pos := screen_to_render_pos(pos) - - px_size := size - - rl_font := to_rl_Font(font, px_size ) - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) - rl.DrawTextCodepoints( rl_font, - raw_data(runes), cast(i32) len(runes), - position = transmute(rl.Vector2) pos, - fontSize = px_size, - spacing = 0.0, - tint = color ); - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) -} - -draw_text_screenspace :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) -{ - // profile(#procedure) - state := get_state(); using state - - if len( content.str ) == 0 { - return - } - font := font - if font.key == Font_Default.key { - font = default_font - } - pos := pos - - rl_font := to_rl_Font(font, size ) - runes := content.runes - - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) - rl.DrawTextCodepoints( rl_font, - raw_data(runes), cast(i32) len(runes), - position = transmute(rl.Vector2) pos, - fontSize = size, - spacing = 0.0, - tint = color ); - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) -} - -ws_view_draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) -{ - // profile(#procedure) - state := get_state(); using state - - if len( content ) == 0 { - return - } - runes, alloc_error := to_runes( content, frame_allocator() ) - verify( alloc_error == AllocatorError.None, "Failed to temp allocate runes" ) - - font := font - if font.key == Font_Default.key { - // if len(font) == 0 { - font = default_font - } - pos := ws_view_to_render_pos(pos) - - px_size := size - zoom_adjust := px_size * project.workspace.cam.zoom - - rl_font := to_rl_Font(font, zoom_adjust ) - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) - rl.DrawTextCodepoints( rl_font, - raw_data(runes), cast(i32) len(runes), - position = transmute(rl.Vector2) pos, - fontSize = px_size, - spacing = 0.0, - tint = color ); - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) -} - -when true -{ - ws_view_draw_text_StrRunesPair :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) - { - profile(#procedure) - state := get_state(); using state - - if len( content.str ) == 0 { - return - } - font := font - if font.key == Font_Default.key { - font = default_font - } - pos := ws_view_to_render_pos(pos) - - px_size := size - zoom_adjust := px_size * project.workspace.cam.zoom - rl_font := to_rl_Font(font, zoom_adjust ) - runes := content.runes - - profile_begin("raylib draw codepoints related") - // rl.DrawTextCodepoints( rl_font, - // raw_data(runes), cast(i32) len(runes), - // position = transmute(rl.Vector2) pos, - // fontSize = px_size, - // spacing = 0.0, - // tint = color ); - rl.DrawTextEx(rl_font, - strings.clone_to_cstring(content.str), - position = transmute(rl.Vector2) pos, - fontSize = px_size, - spacing = 0.0, - tint = color - ) - profile_end() - } -} -else -{ - ws_view_draw_text_StrRunesPair :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) - { - profile(#procedure) - state := get_state(); using state - - // We need an alternative way to draw text to the screen (the above is way to expensive) - // Possibly need to watch handmade hero... - - - } -} - -// Raylib's equivalent doesn't take a length for the string (making it a pain in the ass) -// So this is a 1:1 copy except it takes Odin strings -measure_text_size_raylib :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 -{ - // profile(#procedure) - px_size := math.round( points_to_pixels( font_size ) ) - rl_font := to_rl_Font( font, font_size ) - - // This is a static var within raylib. We don't have getter access to it. - // Note(Ed) : raylib font size is in pixels so this is also. - @static text_line_spacing : f32 = 15 - - text_size : Vec2 - - if rl_font.texture.id == 0 || len(text) == 0 { - return text_size - } - - temp_byte_counter : i32 = 0 // Used to count longer text line num chars - byte_counter : i32 = 0 - - text_width : f32 = 0.0 - temp_text_width : f32 = 0.0 // Used to counter longer text line width - - text_height := cast(f32) rl_font.baseSize - scale_factor := px_size / text_height - - letter : rune - index : i32 = 0 - - for id : i32 = 0; id < i32(len(text)); - { - byte_counter += 1 - - next : i32 = 0 - - ctext := cast(cstring) ( & raw_data( text )[id] ) - letter = rl.GetCodepointNext( ctext, & next ) - index = rl.GetGlyphIndex( rl_font, letter ) - - id += 1 - - if letter != rune('\n') - { - if rl_font.glyphs[index].advanceX != 0 { - text_width += f32(rl_font.glyphs[index].advanceX) - } - else { - text_width += rl_font.recs[index].width + f32(rl_font.glyphs[index].offsetX) - } - } - else - { - if temp_text_width < text_width { - temp_text_width = text_width - } - byte_counter = 0 - text_width = 0 - - text_height += text_line_spacing - - if temp_byte_counter < byte_counter { - temp_byte_counter = byte_counter - } - } - } - if temp_text_width < text_width { - temp_text_width = text_width - } - text_size.x = temp_text_width * scale_factor + f32(temp_byte_counter - 1) * spacing - text_size.y = text_height * scale_factor - - return text_size -} -} // when false diff --git a/code/sectr/engine/update.odin b/code/sectr/engine/update.odin index 7911974..e626270 100644 --- a/code/sectr/engine/update.odin +++ b/code/sectr/engine/update.odin @@ -6,8 +6,6 @@ import "core:math/linalg" import "core:os" import str "core:strings" -import rl "vendor:raylib" - DebugActions :: struct { load_project : b32, save_project : b32, @@ -75,12 +73,9 @@ update :: proc( delta_time : f64 ) -> b32 workspace := & project.workspace cam := & workspace.cam - if rl.IsWindowResized() { - window := & state.app_window - window.extent.x = f32(rl.GetScreenWidth()) * 0.5 - window.extent.y = f32(rl.GetScreenHeight()) * 0.5 - - project.workspace.cam.offset = transmute(Vec2) window.extent + window := & state.app_window + if window.resized { + project.workspace.cam.view = transmute(Vec2) window.extent } state.input, state.input_prev = swap( state.input, state.input_prev ) @@ -189,13 +184,13 @@ update :: proc( delta_time : f64 ) -> b32 - cast(f32) i32(debug_actions.cam_move_up) + cast(f32) i32(debug_actions.cam_move_down), } move_velocity *= digital_move_speed * f32(delta_time) - cam.target += move_velocity + cam.position += move_velocity if debug_actions.cam_mouse_pan { if is_within_screenspace(input.mouse.pos) { pan_velocity := input.mouse.delta * vec2(1, -1) * ( 1 / cam.zoom ) - cam.target -= pan_velocity + cam.position -= pan_velocity } } } @@ -252,6 +247,7 @@ update :: proc( delta_time : f64 ) -> b32 debug.last_mouse_pos = input.mouse.pos - should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose() + // should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose() + should_shutdown : b32 = false return should_shutdown } diff --git a/code/sectr/font/provider_raylib.odin b/code/sectr/font/cache.odin similarity index 100% rename from code/sectr/font/provider_raylib.odin rename to code/sectr/font/cache.odin index 98633f0..4da2929 100644 --- a/code/sectr/font/provider_raylib.odin +++ b/code/sectr/font/cache.odin @@ -1,2 +1,2 @@ -package sectr +package sectr diff --git a/code/sectr/font/fontstash.odin b/code/sectr/font/fontstash.odin new file mode 100644 index 0000000..e34643d --- /dev/null +++ b/code/sectr/font/fontstash.odin @@ -0,0 +1,353 @@ +/* +Yet another port of fontstash. + +I decided t use this instead of the odin port as it already deviated from the original impl. +So The code was small enough that I mine as well learn it by porting for my use case. + +Original copyright for fonstash.h: +------------------------------------------------------------------------------ + Copyright (c) 2009-2013 Mikko Mononen memon@inside.org + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +------------------------------------------------------------------------------ +*/ +package sectr + +import stbtt "vendor:stb/truetype" + +Range2_i16 :: struct #raw_union { + using pts : Vec2_i16, + using xy : struct { + x0, y0, x1, y1 : i16, + } +} + +Vec2_i16 :: [2]i16 + +FSTASH_Invalid :: -1 + +FSTASH_Hash_Lut_Size :: 256 +FSTASH_Max_Fallbacks :: 20 +FSTASH_Max_States :: 20 +FSTASH_Vertex_Count :: 1024 +FSTASH_Init_Atlas_Nodes :: 256 + +FSTASH_FontLuts :: [FSTASH_Hash_Lut_Size]i32 +FSTASH_FontFallbacks :: [FSTASH_Max_Fallbacks]i32 + +FSTASH_HandleErrorProc :: #type proc( uptr : rawptr, error, val : i32 ) + +FSTASH_RenderCreateProc :: #type proc( uptr : rawptr, width, height : i32 ) +FSTASH_RenderResizeProc :: #type proc( uptr : rawptr, width, height : i32 ) +FSTASH_RenderUpdateProc :: #type proc( uptr : rawptr, rect : ^i32, data : ^u8 ) +FSTASH_RenderDrawProc :: #type proc( uptr : rawptr, verts : ^f32, tcoords : ^f32, colors : ^i32, num_verts : i32 ) +FSTASH_RenderDelete :: #type proc( uptr : rawptr ) + +FSTASH_AlignFlag :: enum u32 { + Left, + Center, + Right, + Top, + Middle, + Bottom, + Baseline, +} +FSTASH_AlignFlags :: bit_set[ FSTASH_AlignFlag; u32 ] + +// FONSflags +FSTASH_QuadLocation :: enum u32 { + Top_Left = 1, + Bottom_Left = 2, +} + +FSTASH_Atlas :: struct { + dud : i32, +} + +FSTASH_AtlasNode :: struct { + x, y, width : i16, +} + +FSTASH_ErrorCode :: enum u32 { + Atlas_Full, + Scratch_Full, + States_Overflow, + States_Underflow, +} + +FSTASH_Quad :: struct { + x0, y0, s0, t0 : f32, + x1, y1, s1, t1 : f32, +} + +FSTASH_Font :: struct { + info : stbtt.fontinfo, + name : string, + data : []byte, + free_data : bool, + + ascender : f32, + descender : f32, + line_height : f32, + + glyphs : Array(FSTASH_Glyph), + lut : FSTASH_FontLuts, + fallbacks : FSTASH_FontFallbacks, + num_fallbacks : i32, +} + +FSTASH_Glyph :: struct { + codepoint : rune, + index, next : i32, + size, blur : i16, + x_advance : i16, + box : Range2_i16, + offset : Vec2_i16, +} + +FSTASH_Params :: struct { + width, height : i32, + quad_location : FSTASH_QuadLocation, // (flags) + render_create : FSTASH_RenderCreateProc, + render_resize : FSTASH_RenderResizeProc, + render_update : FSTASH_RenderUpdateProc, + render_draw : FSTASH_RenderDrawProc, + render_delete : FSTASH_RenderDelete, +} + +FSTASH_State :: struct { + font : i32, + alignment : i32, + size : f32, + color : [4]u8, + blur : f32, + spacing : f32, +} + +FSTASH_TextIter :: struct { + x, y : f32, + next_x, next_y : f32, + scale, spacing : f32, + + isize, iblur : i16, + + font : ^FSTASH_Font, + prev_glyph_id : i32, + + codepoint : rune, + utf8_state : rune, + + str : string, + next : string, + end : string, +} + +FSTASH_Context :: struct { + params : FSTASH_Params, + +// Atlas + atlas : Array(FSTASH_AtlasNode), + texture_data : []byte, + width, height : i32, +// ---- + + normalized_size : Vec2, + + verts : [FSTASH_Vertex_Count * 2]f32, + tcoords : [FSTASH_Vertex_Count * 2]f32, + colors : [FSTASH_Vertex_Count ]f32, + + states : [FSTASH_Max_States]FSTASH_State, + num_states : i32, + + handle_error : FSTASH_HandleErrorProc, + error_uptr : rawptr, +} + +fstash_decode_utf8 :: proc( state : ^rune, codepoint : ^rune, to_decode : byte ) -> bool +{ + UTF8_Accept :: 0 + UTF8_Reject :: 1 + + @static UTF8_Decode_Table := [?]u8 { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1F + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3F + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5F + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7F + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9F + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // A0..BF + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C0..DF + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, // E0..FF + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, + } + + to_decode_rune := rune(to_decode) + type := UTF8_Decode_Table[to_decode_rune] + + // Update codepoint otherwise initialize it. + (codepoint^) = ((state^) != UTF8_Accept) ? \ + ((to_decode_rune & 0x3F) | ((codepoint^) << 6)) \ + : ((0xFF >> type) & (to_decode_rune)) + + (state^) = cast(rune)(UTF8_Decode_Table[256 + (state^) * 16 + rune(type)]) + return (state^) == UTF8_Accept +} + +fstash_atlas_delete :: proc ( ctx : ^FSTASH_Context ) { + using ctx + array_free( ctx.atlas ) +} + +fstash_atlas_expand :: proc( ctx : ^FSTASH_Context, width, height : i32 ) +{ + if width > ctx.width { + fstash_atlas_insert( ctx, ctx.atlas.num, ctx.width, 0, width - ctx.width ) + } + + ctx.width = width + ctx.height = height +} + +fstash_atlas_init :: proc( ctx : ^FSTASH_Context, width, height : i32, num_nodes : u32 = FSTASH_Init_Atlas_Nodes ) +{ + error : AllocatorError + ctx.atlas, error = array_init_reserve( FSTASH_AtlasNode, context.allocator, u64(num_nodes), dbg_name = "font atlas" ) + ensure(error != AllocatorError.None, "Failed to allocate font atlas") + + ctx.width = width + ctx.height = height + + array_append( & ctx.atlas, FSTASH_AtlasNode{ width = i16(width)} ) +} + +fstash_atlas_insert :: proc( ctx : ^FSTASH_Context, id : u64, x, y, width : i32 ) -> (error : AllocatorError) +{ + error = array_append_at( & ctx.atlas, FSTASH_AtlasNode{ i16(x), i16(y), i16(width) }, id ) + return +} + +fstash_atlas_remove :: proc( ctx : ^FSTASH_Context, id : u64 ) +{ + array_remove_at( ctx.atlas, id ) +} + +fstash_atlas_reset :: proc( ctx : ^FSTASH_Context, width, height : i32 ) +{ + ctx.width = width + ctx.height = height + array_clear( ctx.atlas ) + + array_append( & ctx.atlas, FSTASH_AtlasNode{ width = i16(width)} ) +} + +fstash_atlas_add_skyline_level :: proc (ctx : ^FSTASH_Context, id : u64, x, y, width, height : i32 ) -> (error : AllocatorError) +{ + insert :: fstash_atlas_insert + remove :: fstash_atlas_remove + + error = insert( ctx, id, x, y + height, width) + if error != AllocatorError.None { + ensure( false, "Failed to insert into atlas") + return + } + + // Delete skyline segments that fall under the shadow of the new segment. + for sky_id := id; sky_id < ctx.atlas.num; sky_id += 1 + { + curr := & ctx.atlas.data[sky_id ] + next := & ctx.atlas.data[sky_id + 1] + if curr.x >= next.x + next.width do break + + shrink := i16(next.x + next.width - curr.x) + curr.x += shrink + curr.width -= shrink + + if curr.width > 0 do break + + remove(ctx, sky_id) + sky_id -= 1 + } + + + // Merge same height skyline segments that are next to each other. + for sky_id := id; sky_id < ctx.atlas.num - 1; + { + curr := & ctx.atlas.data[sky_id ] + next := & ctx.atlas.data[sky_id + 1] + + if curr.y == next.y { + curr.width += next.width + remove(ctx, sky_id + 1) + } + else { + sky_id += 1 + } + } + return +} + +fstash_atlas_rect_fits :: proc( ctx : ^FSTASH_Context, location, width, height : i32 ) -> (max_height : i32) +{ + // Checks if there is enough space at the location of skyline span 'i', + // and return the max height of all skyline spans under that at that location, + // (think tetris block being dropped at that position). Or -1 if no space found. + atlas := array_to_slice(ctx.atlas) + node := atlas[location] + + space_left : i32 + if i32(node.x) + width > ctx.width { + max_height = -1 + return + } + + space_left = width; + + y := i32(node.y) + location := location + for ; space_left > 0; + { + if u64(location) == ctx.atlas.num { + max_height = -1 + return + } + + node := atlas[location] + + y := max(y, i32(node.y)) + if y + height > ctx.height { + max_height = -1 + return + } + + space_left -= i32(node.width) + location += 1 + } + max_height = y + return +} + +fstash_atlas_add_rect :: proc( ctx : ^FSTASH_Context, ) +{ + +} diff --git a/code/sectr/font/provider.odin b/code/sectr/font/provider.odin index a690ab7..102c214 100644 --- a/code/sectr/font/provider.odin +++ b/code/sectr/font/provider.odin @@ -1,13 +1,5 @@ package sectr -import "core:fmt" -import "core:math" -import "core:mem" -import "core:path/filepath" -import "core:os" - -import rl "vendor:raylib" - Font_Largest_Px_Size :: 32 Font_Size_Interval :: 2 @@ -35,158 +27,30 @@ FontTag :: struct { point_size : f32 } -FontGlyphsRender :: struct { - size : i32, - count : i32, - padding : i32, - texture : rl.Texture2D, - recs : [^]rl.Rectangle, // Characters rectangles in texture - glyphs : [^]rl.GlyphInfo, // Characters info data -} - FontDef :: struct { - path_file : string, - - // TODO(Ed) : you may have to store font data in the future if we render on demand - // data : []u8, - - default_size : i32, - size_table : [Font_Largest_Px_Size / Font_Size_Interval] FontGlyphsRender, + placeholder : int, } FontProviderData :: struct { - // font_cache : HMapZPL(FontDef), font_cache : HMapChainedPtr(FontDef), } font_provider_startup :: proc() { - profile(#procedure) - state := get_state() - font_provider_data := & get_state().font_provider_data; using font_provider_data - font_cache_alloc_error : AllocatorError - font_cache, font_cache_alloc_error = hmap_chained_init(FontDef, hmap_closest_prime(1 * Kilo), persistent_allocator(), dbg_name = "font_cache" ) - verify( font_cache_alloc_error == AllocatorError.None, "Failed to allocate font_cache" ) - - log("font_cache created") - log("font_provider initialized") } font_provider_shutdown :: proc() { - font_provider_data := & get_state().font_provider_data; using font_provider_data - for & entry in font_cache.lookup - { - if entry == nil do continue - - def := entry.value - for & px_render in def.size_table { - using px_render - rl.UnloadFontData( glyphs, count ) - rl.UnloadTexture ( texture ) - rl.MemFree( recs ) - } - } } -font_load :: proc( path_file : string, +font_load :: proc(ath_file : string, default_size : f32 = Font_Load_Use_Default_Size, desired_id : string = Font_Load_Gen_ID ) -> FontID { profile(#procedure) - log( str_fmt("Loading font: %v", path_file)) - font_provider_data := & get_state().font_provider_data; using font_provider_data - font_data, read_succeded : = os.read_entire_file( path_file ) - verify( b32(read_succeded), str_fmt("Failed to read font file for: %v", path_file) ) - font_data_size := cast(i32) len(font_data) - - desired_id := desired_id - // Use file name as key - if len(desired_id) == 0 { - // NOTE(Ed): This should never be used except for laziness so I'll be throwing a warning everytime. - log("desired_key not provided, using file name. Give it a proper name!", LogLevel.Warning) - // desired_id = cast(FontID) file_name_from_path(path_file) - desired_id = file_name_from_path(path_file) - } - - default_size := default_size - if default_size == Font_Load_Use_Default_Size { - default_size = Font_Default_Point_Size - } - - key := cast(u64) crc32( transmute([]byte) desired_id ) - def, set_error := hmap_chained_set(font_cache, key, FontDef{}) - verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" ) - - def.path_file = path_file - def.default_size = i32(points_to_pixels(default_size)) - - // TODO(Ed): this is slow & eats quite a bit of memory early on. Setup a more on demand load for a specific size. - // Also, we need to eventually switch to a SDF shader for rendering - - // Render all sizes at once - // Note(Ed) : We only generate textures for even multiples of the font. - for font_size : i32 = Font_Size_Interval; font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval - { - profile("font size render") - id := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval) - - px_render := & def.size_table[id - 1] - using px_render - size = font_size - count = 95 // This is the default codepoint count from raylib when loading a font. - padding = Font_TTF_Default_Chars_Padding - glyphs = rl.LoadFontData( raw_data(font_data), font_data_size, - fontSize = size, - codepoints = nil, - codepointCount = count, - type = rl.FontType.DEFAULT ) - verify( glyphs != nil, str_fmt("Failed to load glyphs for font: %v at desired size: %v", desired_id, size ) ) - - atlas := rl.GenImageFontAtlas( glyphs, & recs, count, size, padding, i32(Font_Atlas_Packing_Method.Raylib_Basic) ) - texture = rl.LoadTextureFromImage( atlas ) - - // glyphs_slice := slice_ptr( glyphs, count ) - // for glyph in glyphs_slice { - // TODO(Ed) : See if above can properly reference - - // NOTE(raylib): Update glyphs[i].image to use alpha, required to be used on image_draw_text() - for glyph_id : i32 = 0; glyph_id < count; glyph_id += 1 { - glyph := & glyphs[glyph_id] - - rl.UnloadImage( glyph.image ) - glyph.image = rl.ImageFromImage( atlas, recs[glyph_id] ) - } - rl.UnloadImage( atlas ) - } - - return { key, desired_id } -} - -Font_Use_Default_Size :: f32(0.0) - -to_rl_Font :: proc( id : FontID, size := Font_Use_Default_Size ) -> rl.Font -{ - font_provider_data := & get_state().font_provider_data; using font_provider_data - - even_size := math.round(size * (1.0/f32(Font_Size_Interval))) * f32(Font_Size_Interval) - size := clamp( i32( even_size), 4, Font_Largest_Px_Size ) - def := hmap_chained_get( font_cache, id.key ) - size = size if size != i32(Font_Use_Default_Size) else def.default_size - - id := (size / Font_Size_Interval) + (size % Font_Size_Interval) - px_render := & def.size_table[ id - 1 ] - - rl_font : rl.Font - rl_font.baseSize = px_render.size - rl_font.glyphCount = px_render.count - rl_font.glyphPadding = px_render.padding - rl_font.glyphs = px_render.glyphs - rl_font.recs = px_render.recs - rl_font.texture = px_render.texture - return rl_font + return {} } diff --git a/code/sectr/input/input_sdl.odin b/code/sectr/font/provider_fontstash.odin similarity index 100% rename from code/sectr/input/input_sdl.odin rename to code/sectr/font/provider_fontstash.odin diff --git a/code/sectr/grime/context.odin b/code/sectr/grime/context.odin index 60c121c..c804e7b 100644 --- a/code/sectr/grime/context.odin +++ b/code/sectr/grime/context.odin @@ -1,6 +1,27 @@ package sectr -context_ext :: proc( $ Type : typeid ) -> (^Type) { +context_usr :: #force_inline proc( $ Type : typeid ) -> (^Type) { return cast(^Type) context.user_ptr } +ContextExt :: struct { + stack : StackFixed(rawptr, 1024), +} + +// Assign return value to context.user_ptr +// context_ext_init :: proc() -> rawptr +// { + +// } + +context_ext :: #force_inline proc() -> ^ContextExt { + return cast(^ContextExt) context.user_ptr +} + +context_push :: proc( value : ^($Type) ) { + push( & context_ext().stack, value ) +} + +context_pop :: proc( value : ^($Type) ) { + pop( & context_ext().stack ) +} diff --git a/code/sectr/grime/memory_tracker.odin b/code/sectr/grime/memory_tracker.odin index 47c075d..29f7b63 100644 --- a/code/sectr/grime/memory_tracker.odin +++ b/code/sectr/grime/memory_tracker.odin @@ -19,14 +19,14 @@ MemoryTracker :: struct { Track_Memory :: false -tracker_msg_buffer : [Kilobyte * 16]u8 +// tracker_msg_buffer : [Kilobyte * 16]u8 memtracker_clear :: proc ( tracker : MemoryTracker ) { when ! Track_Memory { return } - temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) - context.temp_allocator = arena_allocator(& temp_arena) + // temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) + // context.temp_allocator = arena_allocator(& temp_arena) logf("Clearing tracker: %v", tracker.name) memtracker_dump_entries(tracker); @@ -38,8 +38,8 @@ memtracker_init :: proc ( tracker : ^MemoryTracker, allocator : Allocator, num_e when ! Track_Memory { return } - temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) - context.temp_allocator = arena_allocator(& temp_arena) + // temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) + // context.temp_allocator = arena_allocator(& temp_arena) tracker.name = name @@ -56,8 +56,8 @@ memtracker_register :: proc( tracker : ^MemoryTracker, new_entry : MemoryTracker return } profile(#procedure) - temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) - context.temp_allocator = arena_allocator(& temp_arena) + // temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) + // context.temp_allocator = arena_allocator(& temp_arena) if tracker.entries.num == tracker.entries.capacity { ensure(false, "Memory tracker entries array full, can no longer register any more allocations") @@ -110,8 +110,8 @@ memtracker_unregister :: proc( tracker : MemoryTracker, to_remove : MemoryTracke return } profile(#procedure) - temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) - context.temp_allocator = arena_allocator(& temp_arena) + // temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) + // context.temp_allocator = arena_allocator(& temp_arena) entries := array_to_slice(tracker.entries) for idx in 0..< tracker.entries.num @@ -139,8 +139,8 @@ memtracker_check_for_collisions :: proc ( tracker : MemoryTracker ) return } profile(#procedure) - temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) - context.temp_allocator = arena_allocator(& temp_arena) + // temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) + // context.temp_allocator = arena_allocator(& temp_arena) entries := array_to_slice(tracker.entries) for idx in 1 ..< tracker.entries.num { @@ -161,8 +161,8 @@ memtracker_dump_entries :: proc( tracker : MemoryTracker ) when ! Track_Memory { return } - temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) - context.temp_allocator = arena_allocator(& temp_arena) + // temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) + // context.temp_allocator = arena_allocator(& temp_arena) log( "Dumping Memory Tracker:") for idx in 0 ..< tracker.entries.num { diff --git a/code/sectr/input/input_sokol.odin b/code/sectr/input/input_sokol.odin new file mode 100644 index 0000000..3876406 --- /dev/null +++ b/code/sectr/input/input_sokol.odin @@ -0,0 +1 @@ +package sectr \ No newline at end of file diff --git a/code/sectr/engine/logger.odin b/code/sectr/logger.odin similarity index 87% rename from code/sectr/engine/logger.odin rename to code/sectr/logger.odin index 42e1452..4689053 100644 --- a/code/sectr/engine/logger.odin +++ b/code/sectr/logger.odin @@ -118,15 +118,22 @@ logger_interface :: proc( str_to_file_ln( logger.file, to_string(builder) ) } -// TODO(Ed): Use a fixed size block allocation for message formatting used by core_log -// This will prevent stack overflows with the virtual arena debug logs at worst case and not need to do -// some inline arena allocation on-site such as with the memory tracker + +// This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators. +Logger_Allocator_Buffer : [32 * Kilobyte]u8 log :: proc( msg : string, level := LogLevel.Info, loc := #caller_location ) { + temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) + context.allocator = arena_allocator(& temp_arena) + context.temp_allocator = arena_allocator(& temp_arena) + core_log.log( level, msg, location = loc ) } logf :: proc( fmt : string, args : ..any, level := LogLevel.Info, loc := #caller_location ) { - // context.allocator = transient_allocator() + temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) + context.allocator = arena_allocator(& temp_arena) + context.temp_allocator = arena_allocator(& temp_arena) + core_log.logf( level, fmt, ..args, location = loc ) } diff --git a/code/sectr/parser/whitespace.odin b/code/sectr/parser/whitespace.odin index e93426e..83671e0 100644 --- a/code/sectr/parser/whitespace.odin +++ b/code/sectr/parser/whitespace.odin @@ -134,7 +134,7 @@ pws_parser_lex :: proc ( text : string, allocator : Allocator ) -> ( PWS_LexResu rune_type :: proc( codepoint : rune ) -> PWS_TokenType { - using self := context_ext( PWS_LexerData) + using self := context_usr( PWS_LexerData) switch codepoint { @@ -177,7 +177,7 @@ pws_parser_lex :: proc ( text : string, allocator : Allocator ) -> ( PWS_LexResu make_token :: proc ( byte_offset : int ) -> AllocatorError { - self := context_ext( PWS_LexerData); using self + self := context_usr( PWS_LexerData); using self if previous_rune == Rune_Carriage_Return && current_rune != Rune_Line_Feed { ensure(false, "Rouge Carriage Return") @@ -268,7 +268,7 @@ pws_parser_parse :: proc( text : string, allocator : Allocator ) -> ( PWS_ParseR //region Helper procs eat_line :: #force_inline proc() { - self := context_ext( PWS_ParseData); using self + self := context_usr( PWS_ParseData); using self tok := cast( ^PWS_Token) head line.type = .Line diff --git a/code/sectr/project/manual_serialization.odin b/code/sectr/project/manual_serialization.odin index 235cdeb..509c66d 100644 --- a/code/sectr/project/manual_serialization.odin +++ b/code/sectr/project/manual_serialization.odin @@ -13,7 +13,7 @@ vec2_json_unmarshal :: proc( value : ^ json.Value ) -> Vec2 { } } -color_json_unmarshal :: proc( value : ^ json.Value ) -> Color { +color_json_unmarshal :: proc( value : ^ json.Value ) -> RGBA8 { json_color := value.(json.Array) r := u8(json_color[0].(json.Float)) g := u8(json_color[1].(json.Float)) diff --git a/code/sectr/space.odin b/code/sectr/space.odin index 4fca96a..1f4f53e 100644 --- a/code/sectr/space.odin +++ b/code/sectr/space.odin @@ -6,8 +6,6 @@ Ultimately the user's window ppcm (pixels-per-centimeter) determins how all virt */ package sectr -import rl "vendor:raylib" - // The points to pixels and pixels to points are our only reference to accurately converting // an object from world space to screen-space. // This prototype engine will have all its spacial unit base for distances in virtual pixels. @@ -97,7 +95,13 @@ range2_pixels_to_cm :: #force_inline proc "contextless"( range : Range2 ) -> Ran //endregion -Camera :: rl.Camera2D +Camera :: struct { + view : Extents2, + position : Vec2, + zoom : f32, +} + +Camera_Default := Camera { zoom = 1 } CameraZoomMode :: enum u32 { Digital, @@ -117,8 +121,8 @@ BoundsCorners2 :: struct { top_left, top_right, bottom_left, bottom_right: Vec2, } -Extents2 :: distinct Vec2 -Extents2i :: distinct Vec2i +Extents2 :: Vec2 +Extents2i :: Vec2i WS_Pos :: struct { tile_id : Vec2i, @@ -162,8 +166,8 @@ view_get_bounds :: #force_inline proc "contextless"() -> Range2 { cam := & project.workspace.cam screen_extent := state.app_window.extent cam_zoom_ratio := 1.0 / cam.zoom - bottom_left := Vec2 { cam.target.x, -cam.target.y } + Vec2 { -screen_extent.x, -screen_extent.y} * cam_zoom_ratio - top_right := Vec2 { cam.target.x, -cam.target.y } + Vec2 { screen_extent.x, screen_extent.y} * cam_zoom_ratio + bottom_left := Vec2 { cam.position.x, -cam.position.y } + Vec2 { -screen_extent.x, -screen_extent.y} * cam_zoom_ratio + top_right := Vec2 { cam.position.x, -cam.position.y } + Vec2 { screen_extent.x, screen_extent.y} * cam_zoom_ratio return range2( bottom_left, top_right ) } @@ -173,10 +177,10 @@ view_get_corners :: #force_inline proc "contextless"() -> BoundsCorners2 { cam := & project.workspace.cam cam_zoom_ratio := 1.0 / cam.zoom screen_extent := state.app_window.extent * cam_zoom_ratio - top_left := cam.target + Vec2 { -screen_extent.x, screen_extent.y } - top_right := cam.target + Vec2 { screen_extent.x, screen_extent.y } - bottom_left := cam.target + Vec2 { -screen_extent.x, -screen_extent.y } - bottom_right := cam.target + Vec2 { screen_extent.x, -screen_extent.y } + top_left := cam.position + Vec2 { -screen_extent.x, screen_extent.y } + top_right := cam.position + Vec2 { screen_extent.x, screen_extent.y } + bottom_left := cam.position + Vec2 { -screen_extent.x, -screen_extent.y } + bottom_right := cam.position + Vec2 { screen_extent.x, -screen_extent.y } return { top_left, top_right, bottom_left, bottom_right } } @@ -196,7 +200,7 @@ render_to_ws_view_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 { screen_to_ws_view_pos :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 { state := get_state(); using state cam := & project.workspace.cam - result := Vec2 { cam.target.x, -cam.target.y} + Vec2 { pos.x, pos.y } * (1 / cam.zoom) + result := Vec2 { cam.position.x, -cam.position.y} + Vec2 { pos.x, pos.y } * (1 / cam.zoom) return result } diff --git a/code/sectr/ui/core/layout.odin b/code/sectr/ui/core/layout.odin index 8a7e094..ab5daf8 100644 --- a/code/sectr/ui/core/layout.odin +++ b/code/sectr/ui/core/layout.odin @@ -62,9 +62,9 @@ UI_LayoutSide :: struct { UI_LayoutFlag :: enum u32 { - // Will perform scissor pass on children to their parent's bounds + // Will NOT perform scissor pass on children to their parent's bounds // (Specified in the parent) - Clip_Children_To_Bounds, + Dont_Clip_Children_To_bounds, // Enforces the box will always remain in a specific position relative to the parent. // Overriding the anchors and margins. diff --git a/code/sectr/ui/core/state.odin b/code/sectr/ui/core/state.odin index afa3797..1cfef42 100644 --- a/code/sectr/ui/core/state.odin +++ b/code/sectr/ui/core/state.odin @@ -86,6 +86,7 @@ UI_Parent_Stack_Size :: 512 // UI_Built_Boxes_Array_Size :: 8 UI_Built_Boxes_Array_Size :: 128 * Kilobyte +// TODO(Ed): Rename to UI_Context UI_State :: struct { // TODO(Ed) : Use these // build_arenas : [2]Arena, diff --git a/code/sectr/ui/core/style.odin b/code/sectr/ui/core/style.odin index 3a2a6df..0073375 100644 --- a/code/sectr/ui/core/style.odin +++ b/code/sectr/ui/core/style.odin @@ -17,8 +17,8 @@ UI_StylePreset :: enum u32 { } UI_Style :: struct { - bg_color : Color, - border_color : Color, + bg_color : RGBA8, + border_color : RGBA8, // TODO(Ed): We cannot support individual corners unless we add it to raylib (or finally change the rendering backend) corner_radii : [Corner.Count]f32, @@ -33,7 +33,7 @@ UI_Style :: struct { // shader : UI_Shader, font : FontID, - text_color : Color, + text_color : RGBA8, // TODO(Ed) : Support setting the cursor state cursor : UI_Cursor,