diff --git a/code/api.odin b/code/api.odin index 54c903a..5f34bf7 100644 --- a/code/api.odin +++ b/code/api.odin @@ -9,6 +9,7 @@ import "core:os" import "core:slice" import "core:strings" import "core:time" +import "core:prof/spall" import rl "vendor:raylib" Path_Assets :: "../assets/" @@ -27,8 +28,11 @@ ModuleAPI :: struct { } @export -startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger ) +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 ) @@ -221,8 +225,11 @@ sectr_shutdown :: proc() } @export -reload :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger ) +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; @@ -234,13 +241,14 @@ reload :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VA context.allocator = persistent_allocator() context.temp_allocator = transient_allocator() + state := get_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. - font_provider_data := & get_state().font_provider_data + font_provider_data := & state.font_provider_data font_provider_data.font_cache.hashes.allocator = persistent_slab_allocator() font_provider_data.font_cache.entries.allocator = persistent_slab_allocator() @@ -257,73 +265,83 @@ swap :: proc( a, b : ^ $Type ) -> ( ^ Type, ^ Type ) { @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 - client_tick := time.tick_now() + should_close : b32 - // Setup Frame Slab + client_tick := time.tick_now() { - alloc_error : AllocatorError - frame_slab, alloc_error = slab_init( & default_slab_policy, bucket_reserve_num = 0, allocator = frame_allocator() ) - verify( alloc_error == .None, "Failed to allocate frame slab" ) + 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() ) + verify( alloc_error == .None, "Failed to allocate frame slab" ) + } + + context.allocator = frame_allocator() + context.temp_allocator = transient_allocator() + + rl.PollInputEvents() + + should_close = update( host_delta_time ) + render() + + rl.SwapScreenBuffer() } - context.allocator = frame_allocator() - context.temp_allocator = transient_allocator() - - rl.PollInputEvents() - - result := update( host_delta_time ) - render() - - rl.SwapScreenBuffer() - - config.engine_refresh_hz = uint(monitor_refresh_hz) - 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 + // Timing { - sleep_ms := frametime_target_ms - frametime_elapsed_ms - pre_sleep_tick := time.tick_now() + // profile("Client tick timing processing") + config.engine_refresh_hz = uint(monitor_refresh_hz) + 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 - if sleep_ms > 0 { - thread_sleep( cast(Duration) sleep_ms * MS_To_NS ) - // thread__highres_wait( sleep_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 - sleep_delta_ns := time.tick_lap_time( & pre_sleep_tick) - sleep_delta_ms := duration_ms( sleep_delta_ns ) + if frametime_elapsed_ms < frametime_target_ms + { + sleep_ms := frametime_target_ms - frametime_elapsed_ms + pre_sleep_tick := time.tick_now() - if sleep_delta_ms < sleep_ms { - // log( str_fmt_tmp("frametime sleep was off by: %v ms", sleep_delta_ms - sleep_ms )) - } + if sleep_ms > 0 { + thread_sleep( cast(Duration) sleep_ms * MS_To_NS ) + // thread__highres_wait( 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 ) + 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 + } + } + + if frametime_elapsed_ms > 60.0 { + log( str_fmt_tmp("Big tick! %v ms", frametime_elapsed_ms), LogLevel.Warning ) } } - - if frametime_elapsed_ms > 60.0 { - log( str_fmt_tmp("Big tick! %v ms", frametime_elapsed_ms), LogLevel.Warning ) - } - - return result + return should_close } @export clean_frame :: proc() { + // profile( #procedure) state := get_state(); using state context.logger = to_odin_logger( & Memory_App.logger ) diff --git a/code/env.odin b/code/env.odin index 9241974..fe3c4d9 100644 --- a/code/env.odin +++ b/code/env.odin @@ -44,8 +44,9 @@ Memory :: struct { // Not for large memory env states snapshot : MemorySnapshot, - replay : ReplayState, - logger : Logger, + replay : ReplayState, + logger : Logger, + profiler : ^SpallProfiler } persistent_allocator :: proc() -> Allocator { diff --git a/code/font_provider.odin b/code/font_provider.odin index cba7507..1027f3d 100644 --- a/code/font_provider.odin +++ b/code/font_provider.odin @@ -8,7 +8,9 @@ import "core:os" import rl "vendor:raylib" -Font_Largest_Px_Size :: 96 +Font_Largest_Px_Size :: 32 + +Font_Size_Interval :: 2 // Font_Default :: "" Font_Default :: FontID { 0, "" } @@ -52,7 +54,7 @@ FontDef :: struct { // data : []u8, default_size : i32, - size_table : [Font_Largest_Px_Size / 2] FontGlyphsRender, + size_table : [Font_Largest_Px_Size / Font_Size_Interval] FontGlyphsRender, } FontProviderData :: struct { @@ -61,6 +63,7 @@ FontProviderData :: struct { font_provider_startup :: proc() { + profile(#procedure) state := get_state() font_provider_data := & get_state().font_provider_data; using font_provider_data @@ -94,6 +97,7 @@ font_load :: proc( path_file : string, desired_id : string = Font_Load_Gen_ID ) -> FontID { + profile(#procedure) font_provider_data := & get_state().font_provider_data; using font_provider_data font_data, read_succeded : = os.read_entire_file( path_file, context.temp_allocator ) @@ -114,7 +118,7 @@ font_load :: proc( path_file : string, default_size = Font_Default_Point_Size } - key := cast(u64) xxh32( transmute([]byte) desired_id ) + key := cast(u64) crc32( transmute([]byte) desired_id ) def, set_error := zpl_hmap_set( & font_cache, key,FontDef {} ) verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" ) @@ -127,9 +131,10 @@ font_load :: proc( path_file : string, // Render all sizes at once // Note(Ed) : We only generate textures for even multiples of the font. - for font_size : i32 = 2; font_size <= Font_Largest_Px_Size; font_size += 2 + for font_size : i32 = Font_Size_Interval; font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval { - id := (font_size / 2) + (font_size % 2) + 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 @@ -170,16 +175,27 @@ 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 * 0.5) * 2.0 - size := clamp( i32( even_size), 8, Font_Largest_Px_Size ) + 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 := zpl_hmap_get( & font_cache, id.key ) size = size if size != i32(Font_Use_Default_Size) else def.default_size - id := (size / 2) + (size % 2) + id := (size / Font_Size_Interval) + (size % Font_Size_Interval) px_render := & def.size_table[ id - 1 ] // This is free for now perf wise... may have to move this out to on a setting change later. - rl.SetTextureFilter( px_render.texture, rl.TextureFilter.TRILINEAR ) + if id <= 8 { + rl.SetTextureFilter( px_render.texture, rl.TextureFilter.POINT ) + } + else if id <= 14 { + rl.SetTextureFilter( px_render.texture, rl.TextureFilter.POINT ) + } + else if id <= 48 { + rl.SetTextureFilter( px_render.texture, rl.TextureFilter.POINT ) + } + else if id > 48 { + rl.SetTextureFilter( px_render.texture, rl.TextureFilter.POINT ) + } rl_font : rl.Font rl_font.baseSize = px_render.size diff --git a/code/grime.odin b/code/grime.odin index 9bb8b2c..4e07225 100644 --- a/code/grime.odin +++ b/code/grime.odin @@ -18,7 +18,7 @@ import "base:runtime" import c "core:c/libc" import "core:dynlib" import "core:hash" - // crc32 :: hash.crc32 + crc32 :: hash.crc32 import "core:hash/xxhash" xxh32 :: xxhash.XXH32 import fmt_io "core:fmt" diff --git a/code/grime_array.odin b/code/grime_array.odin index f1fb0a8..0646990 100644 --- a/code/grime_array.odin +++ b/code/grime_array.odin @@ -70,6 +70,7 @@ array_init_reserve :: proc array_append :: proc( self : ^Array( $ Type), value : Type ) -> AllocatorError { + // profile(#procedure) if self.header.num == self.header.capacity { grow_result := array_grow( self, self.header.capacity ) @@ -206,6 +207,7 @@ array_free :: proc( using self : Array( $ Type ) ) { array_grow :: proc( using self : ^Array( $ Type ), min_capacity : u64 ) -> AllocatorError { + profile(#procedure) new_capacity := array_grow_formula( capacity ) if new_capacity < min_capacity { diff --git a/code/grime_hashmap_zpl.odin b/code/grime_hashmap_zpl.odin index 234d773..ca46485 100644 --- a/code/grime_hashmap_zpl.odin +++ b/code/grime_hashmap_zpl.odin @@ -84,6 +84,7 @@ zpl_hmap_destroy :: proc( using self : ^ HMapZPL( $ Type ) ) { zpl_hmap_get :: proc( using self : ^ HMapZPL( $ Type ), key : u64 ) -> ^ Type { + // profile(#procedure) id := zpl_hmap_find( self, key ).entry_index if id >= 0 { return & entries.data[id].value @@ -113,6 +114,7 @@ zpl_hmap_grow :: proc( using self : ^ HMapZPL( $ Type ) ) -> AllocatorError { zpl_hmap_rehash :: proc( ht : ^ HMapZPL( $ Type ), new_num : u64 ) -> AllocatorError { + profile(#procedure) // For now the prototype should never allow this to happen. ensure( false, "ZPL HMAP IS REHASHING" ) last_added_index : i64 @@ -183,6 +185,7 @@ zpl_hmap_remove_entry :: proc( using self : ^ HMapZPL( $ Type ), id : i64 ) { zpl_hmap_set :: proc( using self : ^ HMapZPL( $ Type), key : u64, value : Type ) -> (^ Type, AllocatorError) { + // profile(#procedure) id : i64 = 0 find_result : HMapZPL_FindResult @@ -237,6 +240,7 @@ zpl_hmap_add_entry :: proc( using self : ^ HMapZPL( $ Type), key : u64 ) -> i64 zpl_hmap_find :: proc( using self : ^ HMapZPL( $ Type), key : u64 ) -> HMapZPL_FindResult { + // profile(#procedure) result : HMapZPL_FindResult = { -1, -1, -1 } if hashes.num > 0 { diff --git a/code/grime_pool_allocator.odin b/code/grime_pool_allocator.odin index cbc330b..26ac9c2 100644 --- a/code/grime_pool_allocator.odin +++ b/code/grime_pool_allocator.odin @@ -91,6 +91,7 @@ pool_destroy :: proc ( using self : Pool ) pool_allocate_buckets :: proc( using self : Pool, num_buckets : uint ) -> AllocatorError { + profile(#procedure) if num_buckets == 0 { return .Invalid_Argument } @@ -124,6 +125,7 @@ pool_allocate_buckets :: proc( using self : Pool, num_buckets : uint ) -> Alloca pool_grab :: proc( using pool : Pool ) -> ( block : []byte, alloc_error : AllocatorError ) { + // profile(#procedure) alloc_error = .None // Check the free-list first for a block @@ -189,6 +191,7 @@ pool_grab :: proc( using pool : Pool ) -> ( block : []byte, alloc_error : Alloca pool_release :: proc( self : Pool, block : []byte, loc := #caller_location ) { + // profile(#procedure) if Pool_Check_Release_Object_Validity { within_bucket := pool_validate_ownership( self, block ) verify( within_bucket, "Attempted to release data that is not within a bucket of this pool", location = loc ) @@ -221,6 +224,7 @@ pool_reset :: proc( using pool : Pool ) pool_validate_ownership :: proc( using self : Pool, block : [] byte ) -> b32 { + profile(#procedure) within_bucket := b32(false) // Compiler Bug : Same as pool_reset diff --git a/code/grime_profiler.odin b/code/grime_profiler.odin new file mode 100644 index 0000000..37a97c5 --- /dev/null +++ b/code/grime_profiler.odin @@ -0,0 +1,23 @@ +package sectr + +import "base:runtime" +import "core:prof/spall" + +SpallProfiler :: struct { + ctx : spall.Context, + buffer : spall.Buffer, +} + +@(deferred_none=profile_end) +profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { + spall._buffer_begin( & Memory_App.profiler.ctx, & Memory_App.profiler.buffer, name, "", loc ) +} + +profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { + spall._buffer_begin( & Memory_App.profiler.ctx, & Memory_App.profiler.buffer, name, "", loc ) +} + +profile_end :: #force_inline proc "contextless" () +{ + spall._buffer_end( & Memory_App.profiler.ctx, & Memory_App.profiler.buffer) +} diff --git a/code/grime_slab_allocator.odin b/code/grime_slab_allocator.odin index 6a25602..43302ee 100644 --- a/code/grime_slab_allocator.odin +++ b/code/grime_slab_allocator.odin @@ -103,6 +103,7 @@ slab_alloc :: proc( using self : Slab, loc := #caller_location ) -> ( data : []byte, alloc_error : AllocatorError ) { + // profile(#procedure) pool : Pool for id in 0 ..< pools.idx { pool = pools.items[id] @@ -130,6 +131,7 @@ slab_alloc :: proc( using self : Slab, slab_free :: proc( using self : Slab, data : []byte, loc := #caller_location ) { + // profile(#procedure) pool : Pool for id in 0 ..< pools.idx { @@ -150,6 +152,7 @@ slab_resize :: proc( using self : Slab, loc := #caller_location ) -> ( new_data : []byte, alloc_error : AllocatorError ) { + // profile(#procedure) old_size := uint( len(data)) pool_resize, pool_old : Pool diff --git a/code/grime_string_interning.odin b/code/grime_string_interning.odin index 5688224..0d112ac 100644 --- a/code/grime_string_interning.odin +++ b/code/grime_string_interning.odin @@ -62,30 +62,35 @@ str_intern :: proc( content : string ) -> StringCached { + // profile(#procedure) cache := get_state().string_cache - key := u64( xxh32( transmute([]byte) content )) + key := u64( crc32( transmute([]byte) content )) result := zpl_hmap_get( & cache.table, key ) if result != nil { return (result ^) } - length := len(content) - // str_mem, alloc_error := alloc( length, mem.DEFAULT_ALIGNMENT ) - str_mem, alloc_error := slab_alloc( cache.slab, uint(length), uint(mem.DEFAULT_ALIGNMENT) ) - verify( alloc_error == .None, "String cache had a backing allocator error" ) + // profile_begin("new entry") + { + length := len(content) + // str_mem, alloc_error := alloc( length, mem.DEFAULT_ALIGNMENT ) + str_mem, alloc_error := slab_alloc( cache.slab, uint(length), uint(mem.DEFAULT_ALIGNMENT) ) + verify( alloc_error == .None, "String cache had a backing allocator error" ) - // copy_non_overlapping( str_mem, raw_data(content), length ) - copy_non_overlapping( raw_data(str_mem), raw_data(content), length ) + // copy_non_overlapping( str_mem, raw_data(content), length ) + copy_non_overlapping( raw_data(str_mem), raw_data(content), length ) - runes : []rune - // runes, alloc_error = to_runes( content, persistent_allocator() ) - runes, alloc_error = to_runes( content, slab_allocator(cache.slab) ) - verify( alloc_error == .None, "String cache had a backing allocator error" ) + runes : []rune + // runes, alloc_error = to_runes( content, persistent_allocator() ) + runes, alloc_error = to_runes( content, slab_allocator(cache.slab) ) + verify( alloc_error == .None, "String cache had a backing allocator error" ) - // result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) byte_slice(str_mem, length), runes } ) - result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) str_mem, runes } ) - verify( alloc_error == .None, "String cache had a backing allocator error" ) + // result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) byte_slice(str_mem, length), runes } ) + result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) str_mem, runes } ) + verify( alloc_error == .None, "String cache had a backing allocator error" ) + } + // profile_end() return (result ^) } diff --git a/code/grime_virtual_arena.odin b/code/grime_virtual_arena.odin index 5bc4bbe..32663d9 100644 --- a/code/grime_virtual_arena.odin +++ b/code/grime_virtual_arena.odin @@ -132,6 +132,7 @@ varena_alloc :: proc( using self : ^VArena, needs_more_committed := commit_left < size_to_allocate if needs_more_committed { + profile("VArena Growing") next_commit_size := growth_policy( commit_used, committed, reserved, size_to_allocate ) alloc_error = virtual_commit( vmem, next_commit_size ) if alloc_error != .None { diff --git a/code/host/host.odin b/code/host/host.odin index 2cdad5f..92c67c5 100644 --- a/code/host/host.odin +++ b/code/host/host.odin @@ -59,6 +59,7 @@ import "core:time" Duration :: time.Duration duration_seconds :: time.duration_seconds thread_sleep :: time.sleep +import "core:prof/spall" import rl "vendor:raylib" import sectr "../." VArena :: sectr.VArena @@ -70,6 +71,7 @@ import sectr "../." logger_init :: sectr.logger_init LogLevel :: sectr.LogLevel log :: sectr.log + SpallProfiler :: sectr.SpallProfiler to_odin_logger :: sectr.to_odin_logger TrackedAllocator :: sectr.TrackedAllocator tracked_allocator :: sectr.tracked_allocator @@ -108,8 +110,9 @@ ClientMemory :: struct { files_buffer : VArena, } -setup_memory :: proc() -> ClientMemory +setup_memory :: proc( profiler : ^SpallProfiler ) -> ClientMemory { + spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure ) memory : ClientMemory; using memory // Setup the static arena for the entire application @@ -211,8 +214,10 @@ unload_sectr_api :: proc( module : ^ sectr.ModuleAPI ) log("Unloaded sectr API") } -sync_sectr_api :: proc( sectr_api : ^sectr.ModuleAPI, memory : ^ClientMemory, logger : ^Logger ) +sync_sectr_api :: proc( sectr_api : ^sectr.ModuleAPI, memory : ^ClientMemory, logger : ^Logger, profiler : ^SpallProfiler ) { + spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure ) + if write_time, result := os.last_write_time_by_name( Path_Sectr_Module ); result == os.ERROR_NONE && sectr_api.write_time != write_time { @@ -223,10 +228,11 @@ sync_sectr_api :: proc( sectr_api : ^sectr.ModuleAPI, memory : ^ClientMemory, lo for ; file_is_locked( Path_Sectr_Debug_Symbols ) && file_is_locked( Path_Sectr_Live_Module ); {} thread_sleep( Millisecond * 100 ) - sectr_api ^ = load_sectr_api( version_id ) + (sectr_api ^) = load_sectr_api( version_id ) verify( sectr_api.lib_version != 0, "Failed to hot-reload the sectr module" ) sectr_api.reload( + profiler, & memory.persistent, & memory.frame, & memory.transient, @@ -240,6 +246,15 @@ main :: proc() state : RuntimeState using state + // Setup profiling + profiler : SpallProfiler + { + buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE) + profiler.ctx = spall.context_create("sectr.spall") + profiler.buffer = spall.buffer_create(buffer_backing) + } + spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure ) + // Generating the logger's name, it will be used when the app is shutting down. path_logger_finalized : string { @@ -268,7 +283,7 @@ main :: proc() log( to_str(builder) ) } - memory := setup_memory() + memory := setup_memory( & profiler ) // TODO(Ed): Cannot use the manually created allocators for the host. Not sure why // Something is wrong with the tracked_allocator init @@ -286,6 +301,7 @@ main :: proc() running = true; sectr_api = sectr_api sectr_api.startup( + & profiler, & memory.persistent, & memory.frame, & memory.transient, @@ -299,8 +315,10 @@ main :: proc() // TODO(Ed) : This should have an end status so that we know the reason the engine stopped. for ; running ; { + spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, "Host Tick" ) + // Hot-Reload - sync_sectr_api( & sectr_api, & memory, & logger ) + sync_sectr_api( & sectr_api, & memory, & logger, & profiler ) running = sectr_api.tick( duration_seconds( delta_ns ), delta_ns ) sectr_api.clean_frame() @@ -318,6 +336,9 @@ main :: proc() sectr_api.shutdown() unload_sectr_api( & sectr_api ) + spall.buffer_destroy( & profiler.ctx, & profiler.buffer ) + spall.context_destroy( & profiler.ctx ) + log("Succesfuly closed") file_close( logger.file ) // TODO(Ed) : Add string interning!!!!!!!!! diff --git a/code/input.odin b/code/input.odin index f3cc534..89a400e 100644 --- a/code/input.odin +++ b/code/input.odin @@ -290,6 +290,7 @@ import rl "vendor:raylib" poll_input :: proc( old, new : ^ InputState ) { + profile(#procedure) input_process_digital_btn :: proc( old_state, new_state : ^ DigitalBtn, is_down : b32 ) { new_state.ended_down = is_down @@ -304,6 +305,7 @@ poll_input :: proc( old, new : ^ InputState ) // Keyboard { + // profile("Keyboard") check_range :: proc( old, new : ^ InputState, start, end : i32 ) { for id := start; id < end; id += 1 @@ -332,6 +334,7 @@ poll_input :: proc( old, new : ^ InputState ) // Mouse { + // profile("Mouse") // Process Buttons for id : i32 = 0; id < i32(MouseBtn.count); id += 1 { diff --git a/code/math.odin b/code/math.odin index cf43e18..6efa7cf 100644 --- a/code/math.odin +++ b/code/math.odin @@ -49,3 +49,12 @@ add_range2 :: #force_inline proc "contextless" ( a, b : Range2 ) -> Range2 { }} return result } + +equal_range2 :: #force_inline proc "contextless" ( a, b : Range2 ) -> b32 { + result := a.p0 == b.p0 && a.p1 == b.p1 + return b32(result) +} + +size_range2 :: #force_inline proc "contextless" ( value : Range2 ) -> Vec2 { + return { value.p1.x - value.p0.x, value.p0.y - value.p1.y } +} \ No newline at end of file diff --git a/code/parser_whitespace.odin b/code/parser_whitespace.odin index 44ddb58..0ab9ee4 100644 --- a/code/parser_whitespace.odin +++ b/code/parser_whitespace.odin @@ -118,6 +118,7 @@ PWS_LexerData :: struct { pws_parser_lex :: proc ( text : string, allocator : Allocator ) -> ( PWS_LexResult, AllocatorError ) { + profile(#procedure) using lexer : PWS_LexerData context.user_ptr = & lexer content = text @@ -234,6 +235,7 @@ PWS_ParseData :: struct { pws_parser_parse :: proc( text : string, allocator : Allocator ) -> ( PWS_ParseResult, AllocatorError ) { + profile(#procedure) using parser : PWS_ParseData context.user_ptr = & result diff --git a/code/text.odin b/code/text.odin index 4d5df93..b7b8809 100644 --- a/code/text.odin +++ b/code/text.odin @@ -6,6 +6,7 @@ import rl "vendor:raylib" 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 { @@ -35,6 +36,7 @@ debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Co 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 { @@ -62,7 +64,9 @@ draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.C tint = color ); } -draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) { +draw_text_string_cached :: proc( content : StringCached, 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 { @@ -91,6 +95,7 @@ draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32, // So this is a 1:1 copy except it takes Odin strings measure_text_size :: 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 ) diff --git a/code/tick_render.odin b/code/tick_render.odin index 10b9249..541e706 100644 --- a/code/tick_render.odin +++ b/code/tick_render.odin @@ -6,6 +6,7 @@ import rl "vendor:raylib" render :: proc() { + profile(#procedure) state := get_state(); using state replay := & Memory_App.replay cam := & project.workspace.cam @@ -21,6 +22,7 @@ render :: proc() render_mode_2d() //region Render Screenspace { + profile("Render Screenspace") fps_msg := str_fmt_tmp( "FPS: %f", 1 / (frametime_elapsed_ms * MS_To_S) ) fps_msg_width := measure_text_size( fps_msg, default_font, 16.0, 0.0 ).x fps_msg_pos := screen_get_corners().top_right - { fps_msg_width, 0 } @@ -95,6 +97,7 @@ render :: proc() render_mode_2d :: proc() { + profile(#procedure) state := get_state(); using state cam := & project.workspace.cam @@ -108,6 +111,7 @@ render_mode_2d :: proc() ImguiRender: { + profile("Imgui Render") ui := & state.project.workspace.ui root := ui.root if root.num_children == 0 { @@ -115,7 +119,9 @@ render_mode_2d :: proc() } current := root.first - for ; current != nil; { + for ; current != nil; + { + profile("Box") parent := current.parent style := current.style @@ -125,6 +131,7 @@ render_mode_2d :: proc() // TODO(Ed) : Render Borders + // profile_begin("Calculating Raylib rectangles") render_bounds := Range2 { pts = { world_to_screen_pos(computed.bounds.min), world_to_screen_pos(computed.bounds.max), @@ -157,15 +164,47 @@ render_mode_2d :: proc() render_content.max.x - render_content.min.x, render_content.max.y - render_content.min.y, } + // profile_end() - rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color ) + draw_rectangle :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style ) { + if style.layout.corner_radii[0] > 0 { + rl.DrawRectangleRounded( rect, style.layout.corner_radii[0], 9, style.bg_color ) + } + else { + rl.DrawRectangleRec( rect, style.bg_color ) + } + } + + draw_rectangle_lines :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style, color : Color, thickness : f32 ) { + if style.layout.corner_radii[0] > 0 { + rl.DrawRectangleRoundedLines( rect, style.layout.corner_radii[0], 9, thickness, color ) + } + else { + rl.DrawRectangleLinesEx( rect, thickness, color ) + } + } + + // profile_begin("rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color )") + if style.bg_color.a != 0 + { + draw_rectangle( rect_bounds, style ) + } + // profile_end() line_thickness := 1 * cam_zoom_ratio - rl.DrawRectangleRoundedLines( rect_padding, style.layout.corner_radii[0], 9, line_thickness, Color_Debug_UI_Padding_Bounds ) - rl.DrawRectangleRoundedLines( rect_content, style.layout.corner_radii[0], 9, line_thickness, Color_Debug_UI_Content_Bounds ) - if .Mouse_Resizable in current.flags + // profile_begin("rl.DrawRectangleRoundedLines: padding & content") + if equal_range2(computed.content, computed.padding) { + // draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Padding_Bounds, line_thickness ) + } + else { + // draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Content_Bounds, line_thickness ) + } + // profile_end() + + if .Mouse_Resizable in current.flags && false { + // profile("Resize Bounds") resize_border_width := cast(f32) get_state().config.ui_resize_border_width resize_percent_width := style.size * (resize_border_width * 1.0/ 200.0) resize_border_non_range := add(current.computed.bounds, range2( @@ -182,12 +221,16 @@ render_mode_2d :: proc() render_resize.max.x - render_resize.min.x, render_resize.max.y - render_resize.min.y, } - rl.DrawRectangleRoundedLines( rect_resize, style.layout.corner_radii[0], 9, line_thickness, Color_Red ) + // rl.DrawRectangleRoundedLines( rect_resize, style.layout.corner_radii[0], 9, line_thickness, Color_Red ) + draw_rectangle_lines( rect_padding, style, Color_Red, line_thickness ) } - point_radius := 3 * cam_zoom_ratio - rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red ) - rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) + point_radius := 2 * cam_zoom_ratio + + // profile_begin("circles") + // rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red ) + // rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) + // profile_end() if len(current.text.str) > 0 { draw_text_string_cached( current.text, world_to_screen_pos(computed.text_pos), style.font_size, style.text_color ) diff --git a/code/tick_update.odin b/code/tick_update.odin index 4e6c154..c631d36 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -29,6 +29,7 @@ DebugActions :: struct { poll_debug_actions :: proc( actions : ^ DebugActions, input : ^ InputState ) { + // profile(#procedure) using actions using input @@ -64,6 +65,7 @@ frametime_delta32 :: #force_inline proc "contextless" () -> f32 { update :: proc( delta_time : f64 ) -> b32 { + profile(#procedure) state := get_state(); using state replay := & Memory_App.replay workspace := & project.workspace @@ -146,6 +148,7 @@ update :: proc( delta_time : f64 ) -> b32 //region Camera Manual Nav { + // profile("Camera Manual Nav") digital_move_speed : f32 = 200.0 if workspace.zoom_target == 0.0 { @@ -190,6 +193,8 @@ update :: proc( delta_time : f64 ) -> b32 //region Imgui Tick { + profile("Imgui Tick") + // Creates the root box node, set its as the first parent. ui_graph_build( & state.project.workspace.ui ) ui := ui_context @@ -236,17 +241,27 @@ update :: proc( delta_time : f64 ) -> b32 // Whitespace AST test when true { + profile("Whitespace AST test") + + text_style := frame_style_default + text_style.flags = { + .Fixed_Position_X, .Fixed_Position_Y, + // .Fixed_Width, .Fixed_Height, + } + text_theme := UI_StyleTheme { styles = { - frame_style_default, - frame_style_default, - frame_style_default, - frame_style_default, + text_style, + text_style, + text_style, + text_style, }} text_theme.default.bg_color = Color_Transparent text_theme.disabled.bg_color = Color_Frame_Disabled text_theme.hovered.bg_color = Color_Frame_Hover text_theme.focused.bg_color = Color_Frame_Select + layout_text := default_layout + ui_style_theme( text_theme ) alloc_error : AllocatorError; success : bool @@ -267,6 +282,8 @@ update :: proc( delta_time : f64 ) -> b32 for line in array_to_slice_num( debug.lorem_parse.lines ) { + profile("WS AST Line") + head := line.first for ; head != nil; { @@ -282,32 +299,30 @@ update :: proc( delta_time : f64 ) -> b32 widget = ui_text( label.str, head.content ) label_id += 1 - layout_text.pos.x += widget.style.layout.size.x + layout_text.pos.x += size_range2( widget.computed.bounds ).x case .Spaces: label := str_intern( str_fmt_alloc( "%v %v", "space", label_id )) - // widget = ui_text( label.str, text_space, {} ) - // widget.style.layout.size = Vec2 { 1, 16 } widget = ui_space( label.str ) label_id += 1 for idx in 1 ..< len( head.content.runes ) { - widget.style.layout.size.x += widget.style.layout.size.x + // TODO(Ed): VIRTUAL WHITESPACE + // widget.style.layout.size.x += range2_size( widget.computed.bounds ) } - layout_text.pos.x += widget.style.layout.size.x + layout_text.pos.x += size_range2( widget.computed.bounds ).x case .Tabs: label := str_intern( str_fmt_alloc( "%v %v", "tab", label_id )) - // widget = ui_text( label.str, text_tab, {} ) widget = ui_tab( label.str ) label_id += 1 for idx in 1 ..< len( head.content.runes ) { - widget.style.layout.size.x += widget.style.layout.size.x + // widget.style.layout.size.x += range2_size( widget.computed.bounds ) } - layout_text.pos.x += widget.style.layout.size.x + layout_text.pos.x += size_range2( widget.computed.bounds ).x } array_append( widgets_ptr, widget ) diff --git a/code/ui.odin b/code/ui.odin index 717c3f9..54c32ed 100644 --- a/code/ui.odin +++ b/code/ui.odin @@ -144,12 +144,14 @@ UI_Layout :: struct { pos : Vec2, // TODO(Ed) : Should everything no matter what its parent is use a WS_Pos instead of a raw vector pos? + size : Vec2, + // If the box is a child of the root parent, its automatically in world space and thus will use the tile_pos. tile_pos : WS_Pos, // TODO(Ed) : Add support for size_to_content? - // size_to_content : b32, - size : Vec2, + size_to_text : b8, + // size_to_content : b8, } UI_Signal :: struct { @@ -341,6 +343,8 @@ ui_box_from_key :: proc( cache : ^HMapZPL(UI_Box), key : UI_Key ) -> (^UI_Box) { ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) { + // profile(#procedure) + using ui := get_state().ui_context key := ui_key_from_string( label ) @@ -348,6 +352,8 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) curr_box : (^ UI_Box) prev_box := zpl_hmap_get( prev_cache, cast(u64) key ) { + // profile("Assigning current box") + set_result : ^ UI_Box set_error : AllocatorError if prev_box != nil { @@ -388,7 +394,8 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) return curr_box } -ui_box_tranverse_next :: proc( box : ^ UI_Box ) -> (^ UI_Box) { +ui_box_tranverse_next :: #force_inline proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box) +{ // If current has children, do them first if box.first != nil { return box.first @@ -423,6 +430,8 @@ ui_drag_delta :: #force_inline proc "contextless" () -> Vec2 { ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) { + profile(#procedure) + get_state().ui_context = ui using get_state().ui_context @@ -444,6 +453,8 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) // TODO(Ed) :: Is this even needed? ui_graph_build_end :: proc() { + profile(#procedure) + ui_parent_pop() // Should be ui_context.root // Regenerate the computed layout if dirty @@ -457,8 +468,25 @@ ui_graph_build :: proc( ui : ^ UI_State ) { ui_graph_build_begin( ui ) } -ui_key_from_string :: proc( value : string ) -> UI_Key { - key := cast(UI_Key) xxh32( transmute([]byte) value ) +ui_key_from_string :: proc( value : string ) -> UI_Key +{ + // profile(#procedure) + USE_RAD_DEBUGGERS_METHOD :: true + + key : UI_Key + + when USE_RAD_DEBUGGERS_METHOD { + hash : u64 + for str_byte in transmute([]byte) value { + hash = ((hash << 5) + hash) + u64(str_byte) + } + key = cast(UI_Key) hash + } + + when ! USE_RAD_DEBUGGERS_METHOD { + key = cast(UI_Key) crc32( transmute([]byte) value ) + } + return key } diff --git a/code/ui_layout.odin b/code/ui_layout.odin index cd8a0a7..35153c8 100644 --- a/code/ui_layout.odin +++ b/code/ui_layout.odin @@ -2,6 +2,7 @@ package sectr ui_compute_layout :: proc() { + profile(#procedure) state := get_state() root := state.project.workspace.ui.root @@ -21,6 +22,7 @@ ui_compute_layout :: proc() current := root.first for ; current != nil; { + profile("Layout Box") parent := current.parent parent_content := parent.computed.content computed := & current.computed @@ -46,6 +48,15 @@ ui_compute_layout :: proc() pos.y += margins.p1.y * anchor.y1 } + text_size : Vec2 + // If the computed matches, we alreayd have the size, don't bother. + // if computed.text_size.y == style.font_size { + if current.first_frame || ! style.size_to_text || computed.text_size.y != size_range2(computed.bounds).y { + text_size = cast(Vec2) measure_text_size( current.text.str, style.font, style.font_size, 0 ) + } else { + text_size = computed.text_size + } + size : Vec2 if UI_StyleFlag.Fixed_Width in style.flags { size.x = layout.size.x @@ -53,6 +64,7 @@ ui_compute_layout :: proc() else { // TODO(Ed) : Not sure what todo here... } + if UI_StyleFlag.Fixed_Height in style.flags { size.y = layout.size.y } @@ -60,6 +72,10 @@ ui_compute_layout :: proc() // TODO(Ed) : Not sure what todo here... } + if style.size_to_text { + size = text_size + } + half_size := size * 0.5 size_bounds := range2( Vec2 {}, @@ -94,12 +110,11 @@ ui_compute_layout :: proc() // Text if len(current.text.str) > 0 { + // profile("Text") top_left := content.p0 bottom_right := content.p1 content_size := Vec2 { top_left.x - bottom_right.x, top_left.y - bottom_right.y } - text_size := cast(Vec2) measure_text_size( current.text.str, style.font, style.font_size, 0 ) - text_pos : Vec2 text_pos = top_left text_pos.x += (-content_size.x - text_size.x) * layout.text_alignment.x diff --git a/code/ui_signal.odin b/code/ui_signal.odin index cb99251..b9a7035 100644 --- a/code/ui_signal.odin +++ b/code/ui_signal.odin @@ -2,6 +2,7 @@ package sectr ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal { + // profile(#procedure) ui := get_state().ui_context input := get_state().input @@ -10,6 +11,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal signal := UI_Signal {} // Cursor Collision + // profile_begin( "Cursor collision") signal.cursor_pos = ui_cursor_pos() signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds ) @@ -22,7 +24,9 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal within_resize_range := cast(b8) ! pos_within_range2( signal.cursor_pos, resize_border_non_range ) within_resize_range &= signal.cursor_over within_resize_range &= .Mouse_Resizable in box.flags + // profile_end() + // profile_begin("misc") left_pressed := pressed( input.mouse.left ) left_released := released( input.mouse.left ) @@ -118,11 +122,13 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal ui.hot = UI_Key(0) } } + // profile_end() signal.resizing = cast(b8) is_active && (within_resize_range || ui.active_start_signal.resizing) ui.hot_resizable = cast(b32) (is_hot && within_resize_range) || signal.resizing // State Deltas update + // profile_begin( "state deltas upate") if is_hot { box.hot_delta += frame_delta @@ -153,11 +159,14 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal else { box.disabled_delta = 0 } + // profile_end() signal.dragging = cast(b8) is_active && ( ! within_resize_range && ! ui.active_start_signal.resizing) // Update style if not in default state { + // profile("Update style") + if is_hot { if ! was_hot { diff --git a/code/ui_widgets.odin b/code/ui_widgets.odin index 3b2e9bb..5c7fa5b 100644 --- a/code/ui_widgets.odin +++ b/code/ui_widgets.odin @@ -7,6 +7,8 @@ UI_Widget :: struct { ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget) { + // profile(#procedure) + widget.box = ui_box_make( flags, label ) widget.signal = ui_signal_from_box( widget.box ) return @@ -14,6 +16,8 @@ ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget) ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget) { + // profile(#procedure) + btn_flags := UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus } btn.box = ui_box_make( btn_flags | flags, label ) btn.signal = ui_signal_from_box( btn.box ) @@ -22,20 +26,22 @@ ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widge ui_text :: proc( label : string, content : StringCached, flags : UI_BoxFlags = {} ) -> UI_Widget { + // profile(#procedure) + state := get_state(); using state box := ui_box_make( flags, label ) signal := ui_signal_from_box( box ) - text_size := measure_text_size( content.str, box.style.font, box.style.font_size, 0 ) - - box.text = content - box.style.layout.size = text_size + box.text = content + box.style.layout.size_to_text = true return { box, signal } } ui_space :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget { + // profile(#procedure) + space_str := str_intern( " " ) state := get_state(); using state @@ -43,14 +49,15 @@ ui_space :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget box := ui_box_make( flags, label ) signal := ui_signal_from_box( box ) - text_size := measure_text_size( space_str.str, box.style.font, box.style.font_size, 0 ) - box.text = space_str - box.style.layout.size = text_size + box.text = space_str + box.style.layout.size_to_text = true return { box, signal } } ui_tab :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget { + // profile(#procedure) + tab_str := str_intern( "\t" ) state := get_state(); using state @@ -58,8 +65,8 @@ ui_tab :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget box := ui_box_make( flags, label ) signal := ui_signal_from_box( box ) - text_size := measure_text_size( tab_str.str, box.style.font, box.style.font_size, 0 ) - box.text = tab_str - box.style.layout.size = text_size + box.text = tab_str + + box.style.layout.size_to_text = true return { box, signal } } diff --git a/scripts/build.ps1 b/scripts/build.ps1 index eda5ee1..d96e116 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -158,6 +158,7 @@ push-location $path_root $build_args += $flag_thread_count + $CoreCount_Physical $build_args += $flag_optimize_none # $build_args += $flag_optimize_minimal + # $build_args += $flag_optimize_speed $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows' @@ -225,7 +226,9 @@ push-location $path_root # $build_args += ($flag_collection + $pkg_collection_thirdparty) $build_args += $flag_use_separate_modules $build_args += $flag_thread_count + $CoreCount_Physical - $build_args += $flag_optimize_none + # $build_args += $flag_optimize_none + # $build_args += $flag_optimize_minimal + $build_args += $flag_optimize_speed $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows'