Started to do manual control of the frame timing (no longer using raylib)

This commit is contained in:
2024-03-08 18:45:08 -05:00
parent 191d5076ea
commit 4b026c379a
10 changed files with 286 additions and 82 deletions

View File

@ -8,6 +8,7 @@ import "core:mem/virtual"
import "core:os"
import "core:slice"
import "core:strings"
import "core:time"
import rl "vendor:raylib"
Path_Assets :: "../assets/"
@ -28,6 +29,8 @@ ModuleAPI :: struct {
@export
startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger )
{
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 )
@ -41,6 +44,7 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V
context.allocator = persistent_allocator()
context.temp_allocator = transient_allocator()
// TODO(Ed) : Put on the transient allocator a slab allocator (transient slab)
}
state := new( State, persistent_allocator() )
@ -94,9 +98,14 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V
cam_zoom_sensitivity_digital = 0.2
cam_zoom_sensitivity_smooth = 4.0
engine_refresh_hz = 30
ui_resize_border_width = 20
}
Desired_OS_Scheduler_MS :: 1
sleep_is_granular = set__scheduler_granularity( Desired_OS_Scheduler_MS )
// rl.Odin_SetMalloc( RL_MALLOC )
rl.SetConfigFlags( {
@ -122,7 +131,7 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V
// Determining current monitor and setting the target frametime based on it..
monitor_id = rl.GetCurrentMonitor()
monitor_refresh_hz = rl.GetMonitorRefreshRate( monitor_id )
rl.SetTargetFPS( monitor_refresh_hz )
rl.SetTargetFPS( 60 * 24 )
log( str_fmt_tmp( "Set target FPS to: %v", monitor_refresh_hz ) )
// Basic Font Setup
@ -166,6 +175,14 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V
ui_startup( & workspace.ui, cache_allocator = general_slab_allocator() )
}
}
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.
free_all( transient_allocator() )
}
// For some reason odin's symbols conflict with native foreign symbols...
@ -220,17 +237,60 @@ swap :: proc( a, b : ^ $Type ) -> ( ^ Type, ^ Type ) {
}
@export
tick :: proc( delta_time : f64, delta_ns : Duration ) -> b32
tick :: proc( host_delta_time : f64, host_delta_ns : Duration ) -> b32
{
client_tick := time.tick_now()
context.allocator = frame_allocator()
context.temp_allocator = transient_allocator()
state := get_state(); using state
state := get_state()
state.frametime_delta_ns = delta_ns
state.frametime_delta_seconds = delta_time
rl.PollInputEvents()
result := update( delta_time )
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
{
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
}
}
if frametime_elapsed_ms > 60.0 {
log( str_fmt_tmp("Big tick! %v ms", frametime_elapsed_ms), LogLevel.Warning )
}
return result
}

35
code/chrono.odin Normal file
View File

@ -0,0 +1,35 @@
package sectr
Nanosecond_To_Microsecond :: 1.0 / (1000.0)
Nanosecond_To_Millisecond :: 1.0 / (1000.0 * 1000.0)
Nanosecond_To_Second :: 1.0 / (1000.0 * 1000.0 * 1000.0)
Microsecond_To_Nanosecond :: 1000.0
Microsecond_To_Millisecond :: 1.0 / 1000.0
Microsecond_To_Second :: 1.0 / (1000.0 * 1000.0)
Millisecond_To_Nanosecond :: 1000.0 * 1000.0
Millisecond_To_Microsecond :: 1000.0
Millisecond_To_Second :: 1.0 / 1000.0
Second_To_Nanosecond :: 1000.0 * 1000.0 * 1000.0
Second_To_Microsecnd :: 1000.0 * 1000.0
Second_To_Millisecond :: 1000.0
NS_To_MS :: Nanosecond_To_Millisecond
NS_To_US :: Nanosecond_To_Microsecond
NS_To_S :: Nanosecond_To_Second
US_To_NS :: Microsecond_To_Nanosecond
US_To_MS :: Microsecond_To_Millisecond
US_To_S :: Microsecond_To_Second
MS_To_NS :: Millisecond_To_Nanosecond
MS_To_US :: Millisecond_To_Microsecond
MS_To_S :: Millisecond_To_Second
S_To_NS :: Second_To_Nanosecond
S_To_US :: Second_To_Microsecnd
S_To_MS :: Second_To_Millisecond
Frametime_High_Perf_Threshold_MS :: 1 / 240.0

View File

@ -126,6 +126,8 @@ AppConfig :: struct {
cam_zoom_sensitivity_smooth : f32,
cam_zoom_sensitivity_digital : f32,
engine_refresh_hz : uint,
ui_resize_border_width : uint,
}
@ -148,11 +150,14 @@ State :: struct {
monitor_id : i32,
monitor_refresh_hz : i32,
engine_refresh_hz : i32,
engine_refresh_target : i32,
sleep_is_granular : b32,
frametime_delta_seconds : f64,
frametime_delta_ns : Duration,
frametime_delta_seconds : f64,
frametime_delta_ms : f64,
frametime_delta_ns : Duration,
frametime_target_ms : f64,
frametime_elapsed_ms : f64,
font_firacode : FontID,
font_squidgy_slimes : FontID,
@ -195,12 +200,24 @@ Project :: struct {
workspace : Workspace,
}
Frame :: struct
{
pos : Vec2,
size : Vec2,
ui : ^UI_Box,
}
Workspace :: struct {
name : string,
cam : Camera,
zoom_target : f32,
frames : Array(Frame),
test_frame : Frame,
// TODO(Ed) : The workspace is mainly a 'UI' conceptually...
ui : UI_State,
}
@ -223,8 +240,4 @@ DebugData :: struct {
draggable_box_pos : Vec2,
draggable_box_size : Vec2,
box_original_size : Vec2,
box_resize_started : b32,
ui_drag_delta : Vec2,
ui_drag_start : Vec2,
}

View File

@ -22,6 +22,7 @@ import "core:hash"
import fmt_io "core:fmt"
str_fmt :: fmt_io.printf
str_fmt_tmp :: fmt_io.tprintf
str_fmt_alloc :: fmt_io.aprintf
str_fmt_builder :: fmt_io.sbprintf
str_fmt_buffer :: fmt_io.bprintf
str_to_file_ln :: fmt_io.fprintln
@ -67,7 +68,10 @@ import "core:path/filepath"
import str "core:strings"
str_builder_to_string :: str.to_string
import "core:time"
Duration :: time.Duration
Duration :: time.Duration
duration_seconds :: time.duration_seconds
duration_ms :: time.duration_milliseconds
thread_sleep :: time.sleep
import "core:unicode"
is_white_space :: unicode.is_white_space
import "core:unicode/utf8"

View File

@ -219,7 +219,7 @@ varena_allocator_proc :: proc(
old_memory_offset := uintptr(old_memory) + uintptr(old_size)
current_offset := uintptr(arena.reserve_start) + uintptr(arena.commit_used)
verify( old_memory_offset != current_offset, "Cannot resize existing allocation in vitual arena to a larger size unless it was the last allocated" )
verify( old_memory_offset == current_offset, "Cannot resize existing allocation in vitual arena to a larger size unless it was the last allocated" )
new_region : []byte
new_region, alloc_error = varena_alloc( arena, size - old_size, alignment, (mode != .Resize_Non_Zeroed), location )
@ -228,7 +228,7 @@ varena_allocator_proc :: proc(
return
}
data := byte_slice( old_memory, size )
data = byte_slice( old_memory, size )
return
case .Query_Features:

View File

@ -1,52 +0,0 @@
/* Windows Virtual Memory
Windows is the only os getting special vmem definitions
since I want full control of it for debug purposes.
*/
package sectr
import core_virtual "core:mem/virtual"
import win32 "core:sys/windows"
when ODIN_OS == OS_Type.Windows {
WIN32_ERROR_INVALID_ADDRESS :: 487
WIN32_ERROR_COMMITMENT_LIMIT :: 1455
@(require_results)
virtual__reserve ::
proc "contextless" ( base_address : uintptr, size : uint ) -> ( vmem : VirtualMemoryRegion, alloc_error : AllocatorError )
{
header_size :: cast(uint) size_of(VirtualMemoryRegion)
result := win32.VirtualAlloc( rawptr(base_address), header_size + size, win32.MEM_RESERVE, win32.PAGE_READWRITE )
if result == nil {
alloc_error = .Out_Of_Memory
return
}
result = win32.VirtualAlloc( rawptr(base_address), header_size, win32.MEM_COMMIT, win32.PAGE_READWRITE )
if result == nil
{
switch err := win32.GetLastError(); err
{
case 0:
alloc_error = .Invalid_Argument
return
case WIN32_ERROR_INVALID_ADDRESS, WIN32_ERROR_COMMITMENT_LIMIT:
alloc_error = .Out_Of_Memory
return
}
alloc_error = .Out_Of_Memory
return
}
vmem.base_address = cast(^VirtualMemoryRegionHeader) result
vmem.reserve_start = memory_after_header(vmem.base_address)
vmem.reserved = size
vmem.committed = header_size
alloc_error = .None
return
}
} // END: ODIN_OS == runtime.Odin_OS_Type.Windows

112
code/grime_windows.odin Normal file
View File

@ -0,0 +1,112 @@
package sectr
import "core:c"
import "core:c/libc"
import "core:fmt"
import core_virtual "core:mem/virtual"
import "core:strings"
import win32 "core:sys/windows"
when ODIN_OS == OS_Type.Windows {
thread__highres_wait :: proc( desired_ms : f64, loc := #caller_location ) -> b32
{
// label_backing : [1 * Megabyte]u8
// label_arena : Arena
// arena_init( & label_arena, slice_ptr( & label_backing[0], len(label_backing)) )
// label_u8 := str_fmt_tmp( "SECTR: WAIT TIMER")//, allocator = arena_allocator( &label_arena) )
// label_u16 := win32.utf8_to_utf16( label_u8, context.temp_allocator) //arena_allocator( & label_arena) )
timer := win32.CreateWaitableTimerExW( nil, nil, win32.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, win32.TIMER_ALL_ACCESS )
if timer == nil {
msg := str_fmt_tmp("Failed to create win32 timer - ErrorCode: %v", win32.GetLastError() )
log( msg, LogLevel.Warning, loc)
return false
}
due_time := win32.LARGE_INTEGER(desired_ms * MS_To_NS)
result := win32.SetWaitableTimerEx( timer, & due_time, 0, nil, nil, nil, 0 )
if ! result {
msg := str_fmt_tmp("Failed to set win32 timer - ErrorCode: %v", win32.GetLastError() )
log( msg, LogLevel.Warning, loc)
return false
}
WAIT_ABANDONED : win32.DWORD : 0x00000080
WAIT_IO_COMPLETION : win32.DWORD : 0x000000C0
WAIT_OBJECT_0 : win32.DWORD : 0x00000000
WAIT_TIMEOUT : win32.DWORD : 0x00000102
WAIT_FAILED : win32.DWORD : 0xFFFFFFFF
wait_result := win32.WaitForSingleObjectEx( timer, win32.INFINITE, win32.BOOL(true) )
switch wait_result
{
case WAIT_ABANDONED:
msg := str_fmt_tmp("Failed to wait for win32 timer - Error: WAIT_ABANDONED" )
log( msg, LogLevel.Error, loc)
return false
case WAIT_IO_COMPLETION:
msg := str_fmt_tmp("Waited for win32 timer: Ended by APC queued to the thread" )
log( msg, LogLevel.Error, loc)
return false
case WAIT_OBJECT_0:
msg := str_fmt_tmp("Waited for win32 timer- Reason : WAIT_OBJECT_0" )
log( msg, loc = loc)
return false
case WAIT_FAILED:
msg := str_fmt_tmp("Waited for win32 timer failed - ErrorCode: $v", win32.GetLastError() )
log( msg, LogLevel.Error, loc)
return false
}
return true
}
set__scheduler_granularity :: proc "contextless" ( desired_ms : u32 ) -> b32 {
return win32.timeBeginPeriod( desired_ms ) == win32.TIMERR_NOERROR
}
WIN32_ERROR_INVALID_ADDRESS :: 487
WIN32_ERROR_COMMITMENT_LIMIT :: 1455
@(require_results)
virtual__reserve ::
proc "contextless" ( base_address : uintptr, size : uint ) -> ( vmem : VirtualMemoryRegion, alloc_error : AllocatorError )
{
header_size :: cast(uint) size_of(VirtualMemoryRegion)
result := win32.VirtualAlloc( rawptr(base_address), header_size + size, win32.MEM_RESERVE, win32.PAGE_READWRITE )
if result == nil {
alloc_error = .Out_Of_Memory
return
}
result = win32.VirtualAlloc( rawptr(base_address), header_size, win32.MEM_COMMIT, win32.PAGE_READWRITE )
if result == nil
{
switch err := win32.GetLastError(); err
{
case 0:
alloc_error = .Invalid_Argument
return
case WIN32_ERROR_INVALID_ADDRESS, WIN32_ERROR_COMMITMENT_LIMIT:
alloc_error = .Out_Of_Memory
return
}
alloc_error = .Out_Of_Memory
return
}
vmem.base_address = cast(^VirtualMemoryRegionHeader) result
vmem.reserve_start = memory_after_header(vmem.base_address)
vmem.reserved = size
vmem.committed = header_size
alloc_error = .None
return
}
} // END: ODIN_OS == runtime.Odin_OS_Type.Windows

View File

@ -293,18 +293,19 @@ main :: proc()
delta_ns : Duration
host_tick := time.tick_now()
// TODO(Ed) : This should have an end status so that we know the reason the engine stopped.
for ; running ;
{
start_tick := time.tick_now()
// Hot-Reload
sync_sectr_api( & sectr_api, & memory, & logger )
running = sectr_api.tick( duration_seconds( delta_ns ), delta_ns )
sectr_api.clean_frame()
delta_ns = time.tick_lap_time( & start_tick )
delta_ns = time.tick_lap_time( & host_tick )
host_tick = time.tick_now()
}
// Determine how the run_cyle completed, if it failed due to an error,
@ -318,5 +319,7 @@ main :: proc()
log("Succesfuly closed")
file_close( logger.file )
file_rename( logger.file_path, path_logger_finalized )
// TODO(Ed) : Add string interning!!!!!!!!!
// file_rename( logger.file_path, path_logger_finalized )
file_rename( str_fmt_tmp( "%s/sectr.log", Path_Logs), path_logger_finalized )
}

View File

@ -21,7 +21,7 @@ render :: proc()
render_mode_2d()
//region Render Screenspace
{
fps_msg := str_fmt_tmp( "FPS: %v", rl.GetFPS() )
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 }
debug_draw_text( fps_msg, fps_msg_pos, 16.0, color = rl.GREEN )
@ -43,15 +43,18 @@ render :: proc()
position.y += debug.draw_debug_text_y
content := str_fmt_buffer( draw_text_scratch[:], format, ..args )
debug_draw_text( content, position, 16.0 )
debug_draw_text( content, position, 14.0 )
debug.draw_debug_text_y += 16
debug.draw_debug_text_y += 14
}
// Debug Text
{
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")
}
@ -70,10 +73,6 @@ render :: proc()
rl.DrawCircleV( cursor_pos, 10, Color_White_A125 )
}
debug_text( "ui_drag_start : %v", debug.ui_drag_start )
debug_text( "ui_drag_delta : %v", debug.ui_drag_delta )
debug_text( "Draggable Box Pos: %v", debug.draggable_box_pos )
debug.draw_debug_text_y = 50
}
//endregion Render Screenspace