From fa25081d630aaae2e53a15fb5264941a17d5c23d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 15 Oct 2025 17:21:37 -0400 Subject: [PATCH] WIP: Getting some of the math sorted out and setting up tick_frametime --- code2/host/host.odin | 13 +- code2/sectr/engine/client_api.odin | 96 ++++++--- code2/sectr/engine/host_api.odin | 4 +- code2/sectr/math.odin | 305 +++++++++++++++++++++++++++++ code2/sectr/pkg_mappings.odin | 128 +++++++++++- code2/sectr/space.odin | 57 ++++++ code2/sectr/state.odin | 73 ++++++- 7 files changed, 643 insertions(+), 33 deletions(-) create mode 100644 code2/sectr/math.odin create mode 100644 code2/sectr/space.odin diff --git a/code2/host/host.odin b/code2/host/host.odin index b359175..1ad97af 100644 --- a/code2/host/host.odin +++ b/code2/host/host.odin @@ -17,7 +17,7 @@ load_client_api :: proc(version_id: int) -> (loaded_module: Client_API) { panic_contextless( "Could not resolve the last write time for sectr") } //TODO(Ed): Lets try to minimize this... - thread_sleep( Millisecond * 100 ) + thread_sleep( Millisecond * 50 ) // Get the live dll loaded up file_copy_sync( Path_Sectr_Module, Path_Sectr_Live_Module, allocator = context.temp_allocator ) did_load: bool; lib, did_load = os_lib_load( Path_Sectr_Live_Module ) @@ -138,8 +138,8 @@ main :: proc() } } barrier_init(& host_memory.lane_job_sync, THREAD_TICK_LANES + THREAD_JOB_WORKERS) - host_tick_lane() } + host_tick_lane() if thread_memory.id == .Master_Prepper { thread_join_multiple(.. host_memory.threads[1:THREAD_TICK_LANES + THREAD_JOB_WORKERS]) @@ -183,7 +183,6 @@ host_tick_lane :: proc() for ; sync_load(& host_memory.tick_running, .Relaxed); { profile("Host Tick") - sync_client_api() running: b64 = host_memory.client_api.tick_lane( duration_seconds(delta_ns), delta_ns ) == false if thread_memory.id == .Master_Prepper { @@ -194,7 +193,8 @@ host_tick_lane :: proc() delta_ns = time_tick_lap_time( & host_tick ) host_tick = time_tick_now() - leader := barrier_wait(& host_memory.lane_sync) + // Lanes are synced before doing running check.. + sync_client_api() } host_lane_shutdown() } @@ -218,12 +218,10 @@ host_job_worker_entrypoint :: proc(worker_thread: ^SysThread) host_memory.client_api.tick_lane_startup(& thread_memory) grime_set_profiler_thread_buffer(& thread_memory.spall_buffer) } - // TODO(Ed): We should make sure job system can never be set to "not running" without first draining jobs. for ; sync_load(& host_memory.job_system.running, .Relaxed); { profile("Host Job Tick") host_memory.client_api.jobsys_worker_tick() - // TODO(Ed): We cannot allow job threads to enter the reload barrier until all jobs have drained. if sync_load(& host_memory.client_api_hot_reloaded, .Acquire) { // Signals to main hread when all jobs have drained. leader :=barrier_wait(& host_memory.job_hot_reload_sync) @@ -232,6 +230,7 @@ host_job_worker_entrypoint :: proc(worker_thread: ^SysThread) host_memory.client_api.hot_reload(& host_memory, & thread_memory) } } + // Were exiting, wait for tick lanes. leader := barrier_wait(& host_memory.lane_job_sync) } @@ -256,7 +255,7 @@ sync_client_api :: proc() // Wait for pdb to unlock (linker may still be writting) for ; file_is_locked( Path_Sectr_Debug_Symbols ) && file_is_locked( Path_Sectr_Live_Module ); {} - thread_sleep( Millisecond * 100 ) + thread_sleep( Millisecond * 50 ) host_memory.client_api = load_client_api( version_id ) verify( host_memory.client_api.lib_version != 0, "Failed to hot-reload the sectr module" ) diff --git a/code2/sectr/engine/client_api.odin b/code2/sectr/engine/client_api.odin index ae8a475..ddc1b18 100644 --- a/code2/sectr/engine/client_api.odin +++ b/code2/sectr/engine/client_api.odin @@ -35,12 +35,9 @@ then prepare for multi-threaded "laned" tick: thread_wide_startup. startup :: proc(host_mem: ^ProcessMemory, thread_mem: ^ThreadMemory) { // Rad Debugger driving me crazy.. - for ; memory == nil; { - memory = host_mem - } - for ; thread == nil; { - thread = thread_mem - } + // NOTE(Ed): This is problably not necessary, they're just loops for my sanity. + for ; memory == nil; { memory = host_mem } + for ; thread == nil; { thread = thread_mem } grime_set_profiler_module_context(& memory.spall_context) grime_set_profiler_thread_buffer(& thread.spall_buffer) profile(#procedure) @@ -61,19 +58,21 @@ hot_reload :: proc(host_mem: ^ProcessMemory, thread_mem: ^ThreadMemory) grime_set_profiler_module_context(& memory.spall_context) } else { - for ; memory == nil; { - sync_load(& memory, .Acquire) - } - for ; thread == nil; { - thread = thread_mem - } + // NOTE(Ed): This is problably not necessary, they're just loops for my sanity. + for ; memory == nil; { sync_load(& memory, .Acquire) } + for ; thread == nil; { thread = thread_mem } } grime_set_profiler_thread_buffer(& thread.spall_buffer) } profile(#procedure) // Do hot-reload stuff... { - + // Test dispatching 64 jobs during hot_reload loop (when the above store is uncommented) + for job_id := 1; job_id < 64; job_id += 1 { + memory.job_info_reload[job_id].id = job_id + memory.job_reload[job_id] = make_job_raw(& memory.job_group_reload, & memory.job_info_reload[job_id], test_job, {}, "Job Test (Hot-Reload)") + job_dispatch_single(& memory.job_reload[job_id], .Normal) + } } // Critical reference synchronization { @@ -82,9 +81,8 @@ hot_reload :: proc(host_mem: ^ProcessMemory, thread_mem: ^ThreadMemory) sync_store(& memory.client_api_hot_reloaded, false, .Release) } else { - for ; memory.client_api_hot_reloaded == true; { - sync_load(& memory.client_api_hot_reloaded, .Acquire) - } + // NOTE(Ed): This is problably not necessary, they're just loops for my sanity. + for ; memory.client_api_hot_reloaded == true; { sync_load(& memory.client_api_hot_reloaded, .Acquire) } } leader = barrier_wait(& memory.lane_job_sync) } @@ -134,12 +132,6 @@ tick_lane :: proc(host_delta_time_ms: f64, host_delta_ns: Duration) -> (should_c timer += host_delta_time_ms sync_store(& should_close, timer > EXIT_TIME, .Release) - // Test dispatching 64 jobs during hot_reload loop (when the above store is uncommented) - for job_id := 1; job_id < 64; job_id += 1 { - memory.job_info_reload[job_id].id = job_id - memory.job_reload[job_id] = make_job_raw(& memory.job_group_reload, & memory.job_info_reload[job_id], test_job, {}, "Job Test (Hot-Reload)") - job_dispatch_single(& memory.job_reload[job_id], .Normal) - } } // profile_end() @@ -205,9 +197,69 @@ test_job :: proc(data: rawptr) // log_print_fmt("Test job succeeded: %v", info.id) } +Frametime_High_Perf_Threshold_MS :: 1 / 240.0 + tick_lane_frametime :: proc() { profile(#procedure) + config := app_config() + frametime := get_frametime() + // context.allocator = frame_slab_allocator() + // context.temp_allocator = transient_allocator() + + profile("Client tick timing processing") + + if thread.id == .Master_Prepper + { + 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( host_delta_ns ) + frametime.elapsed_ms = frametime.delta_ms + host_delta_time_ms + + if frametime.elapsed_ms < frametime.target_ms + { + sleep_ms := frametime.target_ms - frametime.elapsed_ms + pre_sleep_tick := time_tick_now() + + if can_sleep && 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 ) + frametime.fps_avg = 1 / (frametime.avg_ms * MS_To_S) + + if frametime.elapsed_ms > 60.0 { + log_print_fmt("Big tick! %v ms", frametime.elapsed_ms, LoggerLevel.Warning) + } + + frametime.current_frame += 1 + } + else + { + // Non-main thread tick lane timing (since they are in lock-step this should be minimal delta) + + } } @export diff --git a/code2/sectr/engine/host_api.odin b/code2/sectr/engine/host_api.odin index 4273f9e..6ae9cc2 100644 --- a/code2/sectr/engine/host_api.odin +++ b/code2/sectr/engine/host_api.odin @@ -61,6 +61,8 @@ ThreadMemory :: struct { using _: ThreadWorkerContext, // Per-thread profiling - spall_buffer_backing: [SPALL_BUFFER_DEFAULT_SIZE * 2]byte, + spall_buffer_backing: [SPALL_BUFFER_DEFAULT_SIZE * 4]byte, spall_buffer: Spall_Buffer, + + client_memory: ThreadState, } diff --git a/code2/sectr/math.odin b/code2/sectr/math.odin new file mode 100644 index 0000000..a9e80e6 --- /dev/null +++ b/code2/sectr/math.odin @@ -0,0 +1,305 @@ +package sectr + +/* +This is heavy work-in-progress personalized math definitions. + +Desire is for the definitions to be from a geo alg / clifford alg lens instead of linear alg. +Want to maximize use of optimal linear alg operations in the defs though already defined by odin's linear alg library. + +I apologize if this looks terrible my intuiton for math is very sub-par symbolically. +*/ + +import "base:intrinsics" +import "core:math" +import la "core:math/linalg" + +Axis2 :: enum i32 { + Invalid = -1, + X = 0, + Y = 1, + Count, +} + +f32_Infinity :: 0x7F800000 +f32_Min :: 0x00800000 + +// Note(Ed) : I don't see an intrinsict available anywhere for this. So I'll be using the Terathon non-sse impl +// Inverse Square Root +// C++ Source https://github.com/EricLengyel/Terathon-Math-Library/blob/main/TSMath.cpp#L191 +inverse_sqrt_f32 :: proc "contextless" ( value: f32 ) -> f32 +{ + if ( value < f32_Min) { return f32_Infinity } + value_u32 := transmute(u32) value + + initial_approx := 0x5F375A86 - (value_u32 >> 1) + refined_approx := transmute(f32) initial_approx + + // Newton–Raphson method for getting better approximations of square roots + // Done twice for greater accuracy. + refined_approx = refined_approx * (1.5 - value * 0.5 * refined_approx * refined_approx ) + refined_approx = refined_approx * (1.5 - value * 0.5 * refined_approx * refined_approx ) + // refined_approx = (0.5 * refined_approx) * (3.0 - value * refined_approx * refined_approx) + // refined_approx = (0.5 * refined_approx) * (3.0 - value * refined_approx * refined_approx) + return refined_approx +} + +is_power_of_two_u32 :: #force_inline proc "contextless" (value: u32) -> b32 { return value != 0 && ( value & ( value - 1 )) == 0 } + +mov_avg_exp_f32 := #force_inline proc "contextless" (alpha, delta_interval, last_value: f32) -> f32 { return (delta_interval * alpha) + (delta_interval * (1.0 - alpha)) } +mov_avg_exp_f64 := #force_inline proc "contextless" (alpha, delta_interval, last_value: f64) -> f64 { return (delta_interval * alpha) + (delta_interval * (1.0 - alpha)) } + +Q_F4 :: quaternion128 +V2_S4 :: [2]i32 +V3_S4 :: [3]i32 + +M2_F4 :: matrix [2, 2] f32 // Column Major +R2_F4 :: struct { p0, p1: V2_F4 } // Column Major (they are equivalnet) +UR2_F4 :: distinct R2_F4 + +r2f4_zero :: R2_F4 {} + +r2f4 :: #force_inline proc "contextless" (a, b: V2_F4) -> R2_F4 { return R2_F4{a, b} } + +m2f4_from_r2f4 :: #force_inline proc "contextless" (range: R2_F4) -> M2_F4 { return transmute(M2_F4)range } +r2f4_from_m2f4 :: #force_inline proc "contextless" (m: M2_F4) -> R2_F4 { return transmute(R2_F4)m } + +add_r2f4 :: #force_inline proc "contextless" (a, b: R2_F4) -> R2_F4 { return r2f4_from_m2f4(m2f4_from_r2f4(a) + m2f4_from_r2f4(b)) } +sub_r2f4 :: #force_inline proc "contextless" (a, b: R2_F4) -> R2_F4 { return r2f4_from_m2f4(m2f4_from_r2f4(a) - m2f4_from_r2f4(b)) } +equal_r2f4 :: #force_inline proc "contextless" (a, b: R2_F4) -> b32 { result := a.p0 == b.p0 && a.p1 == b.p1; return b32(result) } + +// Will resolve the largest range possible given a & b. +join_r2f4 :: #force_inline proc "contextless" (a, b: R2_F4) -> (joined : R2_F4) { joined.p0 = min( a.min, b.min ); joined.p1 = max( a.max, b.max ); return } +size_range2 :: #force_inline proc "contextless" (value: R2_F4) -> V2_F4 { return { abs( value.p1.x - value.p0.x ), abs( value.p0.y - value.p1.y ) } } + +cross_s :: la.scalar_cross + +/* +V2_F2: 2D Vector (4-Byte Float) 4D Extension (x, y, z : 0, w : 0) +BV2_F2: 2D Bivector (4-Byte Float) +T2_F2: 3x3 Matrix (4-Byte Float) where 3rd row is always (0, 0, 1) +Rotor2_F4: Rotor 2D (4-Byte Float) s is scalar. +*/ +V2_F4 :: [2]f32 +BiV2_F4 :: distinct f32 +T2_F4 :: matrix [3, 3] f32 +UV2_F4 :: distinct V2_F4 +Rotor2_F4 :: struct { bv: BiV2_F4, s: f32 } + +rotor2f4_to_complex64 :: #force_inline proc "contextless" (rotor: Rotor2_F4) -> complex64 { return transmute(complex64) rotor; } + +v2f4_from_f32s :: #force_inline proc "contextless" (x, y: f32 ) -> V2_F4 { return {x, y} } +v2f4_from_scalar :: #force_inline proc "contextless" (scalar: f32 ) -> V2_F4 { return {scalar, scalar}} +v2f4_from_v2s4 :: #force_inline proc "contextless" (v2i: V2_S4) -> V2_F4 { return {f32(v2i.x), f32(v2i.y)}} +v2s4_from_v2f4 :: #force_inline proc "contextless" (v2: V2_F4) -> V2_S4 { return {i32(v2.x), i32(v2.y) }} + +// vec2_64_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2_64 { return { f64(v2.x), f64(v2.y) }} + +// dot_v2f4 :: #force_inline proc "contextless" (a, b: V2_F4) -> (s: f32) { +// x := a.x * b.x +// y := a.y + b.y +// s = x + y +// return +// } +sdot :: la.scalar_dot +vdot :: la.vector_dot +qdot_f2 :: la.quaternion64_dot +qdot_f4 :: la.quaternion128_dot +qdot_f8 :: la.quaternion256_dot +inner_product :: dot +outer_product :: intrinsics.outer_product + +cross_v2 :: la.vector_cross2 + +/* +PointFlat2 : CGA: 2D flat point (x, y, z) +Line : PGA: 2D line (x, y, z) +*/ + +P2_F4 :: distinct V2_F4 +PF2_F4 :: distinct V3_F4 +L2_F4 :: distinct V3_F4 + +//endregion PGA 2 + +//region PGA 3 +/* +V3_F4: 3D Vector (x, y, z) (3x1) 4D Expression : (x, y, z, 0) +BiV3_F4: 3D Bivector (yz, zx, xy) (3x1) +TriV3_F4: 3D Trivector (xyz) (1x1) +Rotor3: 3D Rotation Versor-Transform (4x1) +Motor3: 3D Rotation & Translation Transform (4x2) +*/ + +V3_F4 :: [3]f32 +V4_F4 :: [4]f32 + +BiV3_F4 :: struct #raw_union { + using _ : struct { yz, zx, xy : f32 }, + using xyz : V3_F4, +} + +TriV3_F4 :: distinct f32 + +Rotor3_F4 :: struct { + using bv: BiV3_F4, + s: f32, // Scalar +} + +Shifter3_F4 :: struct { + using bv: BiV3_F4, + s: f32, // Scalar +} + +Motor3 :: struct { + rotor: Rotor3_F4, + md: Shifter3_F4, +} + +UV3_F4 :: distinct V3_F4 +UV4_F4 :: distinct V4_F4 +UBiV3_F4 :: distinct BiV3_F4 + +//region Vec3 + +v3f4_via_f32s :: #force_inline proc "contextless" (x, y, z: f32) -> V3_F4 { return {x, y, z} } + +// complement_vec3 :: #force_inline proc "contextless" ( v : Vec3 ) -> Bivec3 {return transmute(Bivec3) v} + +cross_v3 :: la.vector_cross3 + +inverse_mag_v3f4 :: #force_inline proc "contextless" (v: V3_F4) -> (result : f32) { square := pow2_v3f4(v); result = inverse_sqrt_f32( square ); return } +magnitude_v3f4 :: #force_inline proc "contextless" (v: V3_F4) -> (mag: f32) { square := pow2_v3f4(v); mag = sqrt(square); return } +normalize_v3f4 :: #force_inline proc "contextless" (v: V3_F4) -> (unit_v: UV3_F4) { unit_v = transmute(UnitVec3) (v * inverse_mag_v3f4(v)); return } + +pow2_v3f4 :: #force_inline proc "contextless" (v: V3_F4) -> (s: f32) { return dot_v3f4(v, v) } + +project_v3f4 :: proc "contextless" (a, b: V3_F4) -> (a_to_b: V3_F4) { panic_contextless("not implemented"); return } +reject_v3f4 :: proc "contextless" (a, b: V3_F4 ) -> (a_from_b: V3_F4) { panic_contextless("not implemented"); return } + +project_v3f4_uv3f4 :: #force_inline proc "contextless" (v: V3_F4, u: UV3_F4) -> (v_to_u: V3_F4) { inner := dot_v3f4(v, u); v_to_u = (transmute(V3_F4) u) * inner; return } +project_uv3f4_v3f4 :: #force_inline proc "contextless" (u : UV3_F4, v : V3_F4) -> (u_to_v: V3_F4) { inner := dot_v3f4(u, v); u_to_v = v * inner; return } + +// Anti-wedge of vectors +regress_v3f4 :: #force_inline proc "contextless" (a, b : V3_F4) -> f32 { return a.x * b.y - a.y * b.x } + +reject_v3f4_uv3f4 :: #force_inline proc "contextless" (v: V3_F4, u: UV3_F4) -> ( v_from_u: V3_F4) { inner := dot_v3f4(v, u); v_from_u = (v - (transmute(Vec3) u)) * inner; return } +reject_uv3f4_v3f4 :: #force_inline proc "contextless" (v: V3_F4, u: UV3_F4) -> ( u_from_v: V3_F4) { inner := dot_v3f4(u, v); u_from_v = ((transmute(Vec3) u) - v) * inner; return } + +// Combines the deimensions that are present in a & b +wedge_v3f4 :: #force_inline proc "contextless" (a, b: V3_F4) -> (bv : BiV3_F4) { + yzx_zxy := a.yzx * b.zxy + zxy_yzx := a.zxy * b.yzx + bv = transmute(Bivec3) (yzx_zxy - zxy_yzx) + return +} + +//endregion Vec3 + +//region Bivec3 +biv3f4_via_f32s :: #force_inline proc "contextless" (yz, zx, xy : f32) -> BiV3_F4 {return { xyz = {yz, zx, xy} }} + +complement_biv3f4 :: #force_inline proc "contextless" (b : BiV3_F4) -> BiV3_F4 {return transmute(BiV3_F4) b.xyz} + +//region Operations isomoprhic to vectors +negate_biv3f4 :: #force_inline proc "contextless" (b : BiV3_F4) -> BiV3_F4 {return transmute(BiV3_F4) -b.xyz} +add_biv3f4 :: #force_inline proc "contextless" (a, b: BiV3_F4) -> BiV3_F4 {return transmute(BiV3_F4) (a.xyz + b.xyz)} +sub_biv3f4 :: #force_inline proc "contextless" (a, b: BiV3_F4) -> BiV3_F4 {return transmute(BiV3_F4) (a.xyz - b.xyz)} +mul_biv3f4 :: #force_inline proc "contextless" (a, b: BiV3_F4) -> BiV3_F4 {return transmute(BiV3_F4) (a.xyz * b.xyz)} +mul_biv3f4_f32 :: #force_inline proc "contextless" (b: BiV3_F4, s: f32) -> BiV3_F4 {return transmute(BiV3_F4) (b.xyz * s)} +mul_f32_biv3f4 :: #force_inline proc "contextless" (s: f32, b: BiV3_F4) -> BiV3_F4 {return transmute(BiV3_F4) (s * b.xyz)} +div_biv3f4_f32 :: #force_inline proc "contextless" (b: BiV3_F4, s: f32) -> BiV3_F4 {return transmute(BiV3_F4) (b.xyz / s)} +inverse_mag_biv3f4 :: #force_inline proc "contextless" (b: BiV3_F4) -> f32 {return inverse_mag_vec3(b.xyz)} +magnitude_biv3f4 :: #force_inline proc "contextless" (b: BiV3_F4) -> f32 {return magnitude_vec3 (b.xyz)} +normalize_biv3f4 :: #force_inline proc "contextless" (b: BiV3_F4) -> UBiV3_F4 {return transmute(UBiV3_F4) normalize_vec3(b.xyz)} +squared_mag_biv3f4 :: #force_inline proc "contextless" (b: BiV3_F4) -> f32 {return pow2_vec3(b.xyz)} +//endregion Operations isomoprhic to vectors + +// The wedge of a bi-vector in 3D vector space results in a Trivector represented as a scalar. +// This scalar usually resolves to zero with six possible exceptions that lead to the negative volume element. +wedge_biv3f4 :: #force_inline proc "contextless" (a, b: BiV3_F4) -> f32 { s := a.yz + b.yz + a.zx + b.zx + a.xy + b.xy; return s } + +// anti-wedge (Combines dimensions that are absent from a & b) +regress_biv3f4 :: #force_inline proc "contextless" (a, b: BiV3_F4) -> V3_F4 {return wedge_vec3(vec3(a), vec3(b))} +regress_biv3f4_v3f4 :: #force_inline proc "contextless" (b: BiV3_F4, v: V3_F4) -> f32 {return regress_vec3(b.xyz, v)} +regress_v3_biv3f4 :: #force_inline proc "contextless" (v: V3_F4, b: BiV3_F4) -> f32 {return regress_vec3(b.xyz, v)} + +//endregion biv3f4 + +//region Rotor3 + +rotor3f4_via_comps_f4 :: proc "contextless" (yz, zx, xy, scalar : f32) -> Rotor3_F4 { return Rotor3 {biv3f4_via_f32s(yz, zx, xy), scalar} } + +rotor3f4_via_bv_s_f4 :: #force_inline proc "contextless" (bv: BiV3_F4, scalar: f32) -> (rotor : Rotor3_F4) { return Rotor3_F4 {bv, scalar} } +// rotor3f4_via_from_to_v3f4 :: #force_inline proc "contextless" (from, to: V3_F4) -> (rotor : Rotor3_F4) { rotor.scalar := 1 + dot( from, to ); return } + +inverse_mag_rotor3f4 :: proc "contextless" (rotor : Rotor3_F4) -> (s : f32) { panic("not implemented"); return } +magnitude_rotor3f4 :: proc "contextless" (rotor : Rotor3_F4) -> (s : f32) { panic("not implemented"); return } +squared_mag_f4 :: proc "contextless" (rotor : Rotor3_F4) -> (s : f32) { panic("not implemented"); return } +reverse_rotor3_f4 :: proc "contextless" (rotor : Rotor3_F4) -> (reversed : Rotor3_F4) { reversed = { negate_biv3f4(rotor.bv), rotor.s }; return } + +//endregion Rotor3 + +//region Flat Projective Geometry + +Point3_F4 :: distinct V3_F4 +PointFlat3_F4 :: distinct V4_F4 +Line3_F4 :: struct { + weight : V3_F4, + bulk : BiV3_F4, +} +Plane3_F4 :: distinct V4_F4 // 4D Anti-vector + +// aka: wedge operation for points +join_point3_f4 :: proc "contextless" (p, q : Point3_F4) -> (l : Line3_F4) { + weight := sub(q, p) + bulk := wedge(vec3(p), vec3(q)) + l = {weight, bulk} + return +} +join_pointflat3_f4 :: proc "contextless" (p, q : PointFlat3_F4) -> (l : Line3_F4) { + weight := vec3( + p.w * q.x - p.x * q.w, + p.w * q.y - p.y * q.w, + p.w * q.z - p.z * q.w + ) + bulk := wedge(vec3(p), vec3(q)) + l = { weight, bulk} + return +} +sub_point3_f4 :: #force_inline proc "contextless" (a, b : Point3_F4) -> (v : V3_F4) { v = v3f4(a) - v3f4(b); return } + +//endregion Flat Projective Geometry + +//region Rational Trig + +quadrance :: proc "contextless" (a, b : Point3_F4) -> (q : f32) { q = pow2( sub(a, b)); return } + +// Assumes the weight component is normalized. +spread :: proc "contextless" (l, m : Line3_F4) -> (s : f32) { s = dot(l.weight, m.weight); return } + +//endregion Rational Trig + +//region Grime +// A dump of equivalent symbol generatioon (because the toolchain can't do it yet) +// Symbol alias tables are in grim.odin + +v3f4_to_biv3f4 :: #force_inline proc "contextless" (v: V3_F4) -> BiV3_F4 {return transmute(Bivec3) v } +biv3f4_to_v3f4 :: #force_inline proc "contextless" (bv: BiV3_F4) -> V3_F4 {return transmute(Vec3) bv } +quatf4_from_rotor3f4 :: #force_inline proc "contextless" (rotor: Rotor3_F4) -> Q_F4 {return transmute(Quat128) rotor} +uv3f4_to_v3f4 :: #force_inline proc "contextless" (v: UV3_F4) -> V3_F4 {return transmute(Vec3) v } +uv4f4_to_v4f4 :: #force_inline proc "contextless" (v: UV4_F4) -> V4_F4 {return transmute(Vec4) v } + +// plane_to_v4f4 :: #force_inline proc "contextless" (p : Plane3_F4) -> V4_F4 {return transmute(V4_F4) p} +point3f4_to_v3f4 :: #force_inline proc "contextless" (p: Point3_F4) -> V3_F4 {return transmute(Vec3) p} +pointflat3f4_to_v3f4 :: #force_inline proc "contextless" (p: PointFlat3_F4) -> V3_F4 {return { p.x, p.y, p.z }} +v3f4_to_point3f4 :: #force_inline proc "contextless" (v: V3_F4) -> Point3_F4 {return transmute(Point3) v} + +cross_v3f4_uv3f4 :: #force_inline proc "contextless" (v: V3_F4, u: UV3_F4) -> V3_F4 {return cross_v3f4(v, transmute(Vec3) u)} +cross_u3f4_v3f4 :: #force_inline proc "contextless" (u: UV3_F4, v: V3_F4) -> V3_F4 {return cross_v3f4(transmute(Vec3) u, v)} + +dot_v3f4_uv3f4 :: #force_inline proc "contextless" (v: V3_F4, unit_v: UV3_F4) -> f32 {return dot_v3f4(v, transmute(Vec3) unit_v)} +dot_uv3f4_vs :: #force_inline proc "contextless" (unit_v: UV3_F4, v: V3_F4) -> f32 {return dot_v3f4(v, transmute(Vec3) unit_v)} + +wedge_v3f4_uv3f4 :: #force_inline proc "contextless" (v : V3_F4, unit_v: UV3_F4) -> BiV3_F4 {return wedge_v3f4(v, transmute(Vec3) unit_v)} +wedge_uv3f4_vs :: #force_inline proc "contextless" (unit_v: UV3_F4, v: V3_F4) -> BiV3_F4 {return wedge_v3f4(transmute(Vec3) unit_v, v)} +//endregion Grime diff --git a/code2/sectr/pkg_mappings.odin b/code2/sectr/pkg_mappings.odin index 430949f..b8249d5 100644 --- a/code2/sectr/pkg_mappings.odin +++ b/code2/sectr/pkg_mappings.odin @@ -3,8 +3,6 @@ package sectr /* All direct non-codebase package symbols should do zero allocations. Any symbol that does must be mapped from the Grime package to properly tirage its allocator to odin's ideomatic interface. - - */ import "base:intrinsics" @@ -64,6 +62,24 @@ Mega :: Kilo * 1024 Giga :: Mega * 1024 Tera :: Giga * 1024 +// chrono + NS_To_MS :: grime.NS_To_MS + NS_To_US :: grime.NS_To_US + NS_To_S :: grime.NS_To_S + + US_To_NS :: grime.US_To_NS + US_To_MS :: grime.US_To_MS + US_To_S :: grime.US_To_S + + MS_To_NS :: grime.MS_To_NS + MS_To_US :: grime.MS_To_US + MS_To_S :: grime.MS_To_S + + S_To_NS :: grime.S_To_NS + S_To_US :: grime.S_To_US + S_To_MS :: grime.S_To_MS + + ensure :: #force_inline proc( condition : b32, msg : string, location := #caller_location ) { if condition do return log_print( msg, LoggerLevel.Warning, location ) @@ -106,3 +122,111 @@ profile_begin :: #force_inline proc "contextless" ( name : string, loc := #calle profile_end :: #force_inline proc "contextless" () { spall._buffer_end( & memory.spall_context, & thread.spall_buffer) } + + + +add :: proc { + add_r2f4, +} + +biv3f4 :: proc { + biv3f4_via_f32s, + v3f4_to_biv3f4, +} +bivec :: biv3f4 + +cross :: proc { + cross_s, + cross_v2, + cross_v3, +} + +dot :: proc { + sdot, + vdot, + qdot_f2, + qdot_f4, + qdot_f8, +} + +is_power_of_two :: proc { + is_power_of_two_u32, + // is_power_of_two_uintptr, +} + +mov_avg_exp :: proc { + mov_avg_exp_f32, + mov_avg_exp_f64, +} + +join :: proc { + join_r2f4, +} + +inverse_sqrt :: proc { + inverse_sqrt_f32, +} + +sub :: proc { + sub_r2f4, + sub_biv3f4, + join_point3_f4, + join_pointflat3_f4, +} + +pow2 :: proc { + pow2_v3f4, +} + +regress :: proc { + regress_biv3f4, +} + +rotor3 :: proc { + rotor3f4_via_comps_f4, + rotor3f4_via_bv_s_f4, + // rotor3f4_via_from_to_v3f4, +} + +quatf4 :: proc { + quatf4_from_rotor3f4, +} + +v2f4 :: proc { + v2f4_from_f32s, + v2f4_from_scalar, + v2f4_from_v2s4, + v2s4_from_v2f4, +} + +v3f4 :: proc { + v3f4_via_f32s, + biv3f4_to_v3f4, + point3f4_to_v3f4, + pointflat3f4_to_v3f4, + uv3f4_to_v3f4, +} + +v2 :: proc { + v2f4_from_f32s, + v2f4_from_scalar, + v2f4_from_v2s4, + v2s4_from_v2f4, +} + +v3 :: proc { + v3f4_via_f32s, + biv3f4_to_v3f4, + point3f4_to_v3f4, + pointflat3f4_to_v3f4, + uv3f4_to_v3f4, +} + +v4 :: proc { + uv4f4_to_v4f4, +} + +wedge :: proc { + wedge_v3f4, + wedge_biv3f4, +} diff --git a/code2/sectr/space.odin b/code2/sectr/space.odin new file mode 100644 index 0000000..7157785 --- /dev/null +++ b/code2/sectr/space.odin @@ -0,0 +1,57 @@ +package sectr + +/* Space +Provides various definitions for converting from one standard of measurement to another. +Provides constructs and transformations in reguards to space. + +Ultimately the user's window ppcm (pixels-per-centimeter) determins how all virtual metric conventions are handled. +*/ + +// 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. + +Inches_To_CM :: cast(f32) 2.54 +Points_Per_CM :: cast(f32) 28.3465 +CM_Per_Point :: cast(f32) 1.0 / DPT_DPCM +CM_Per_Pixel :: cast(f32) 1.0 / DPT_PPCM +DPT_DPCM :: cast(f32) 72.0 * Inches_To_CM // 182.88 points/dots per cm +DPT_PPCM :: cast(f32) 96.0 * Inches_To_CM // 243.84 pixels per cm + +when ODIN_OS == .Windows { + op_default_dpcm :: 72.0 * Inches_To_CM + os_default_ppcm :: 96.0 * Inches_To_CM + // 1 inch = 2.54 cm, 96 inch * 2.54 = 243.84 DPCM +} + +AreaSize :: V2_F4 + +Bounds2 :: struct { + top_left, bottom_right: V2_F4, +} + +BoundsCorners2 :: struct { + top_left, top_right, bottom_left, bottom_right: V2_F4, +} + +E2_F4 :: V2_F4 +E2_S4 :: V2_F4 + +WS_Pos :: struct { + tile_id : V2_S4, + rel : V2_F4, +} + +Camera :: struct { + view : E2_F4, + position : V2_F4, + zoom : f32, +} + +Camera_Default := Camera { zoom = 1 } + +CameraZoomMode :: enum u32 { + Digital, + Smooth, +} + diff --git a/code2/sectr/state.odin b/code2/sectr/state.odin index bbbd85b..a0f9e6f 100644 --- a/code2/sectr/state.odin +++ b/code2/sectr/state.odin @@ -6,5 +6,76 @@ package sectr @(thread_local) thread: ^ThreadMemory //endregion STATIC MEMORy -State :: struct { +MemoryConfig :: struct { + reserve_persistent : uint, + reserve_frame : uint, + reserve_transient : uint, + reserve_filebuffer : uint, + + commit_initial_persistent : uint, + commit_initial_frame : uint, + commit_initial_transient : uint, + commit_initial_filebuffer : uint, } + +// All nobs available for this application +AppConfig :: struct { + using memory : MemoryConfig, + + resolution_width : uint, + resolution_height : uint, + refresh_rate : uint, + + cam_min_zoom : f32, + cam_max_zoom : f32, + cam_zoom_mode : CameraZoomMode, + cam_zoom_smooth_snappiness : f32, + cam_zoom_sensitivity_smooth : f32, + cam_zoom_sensitivity_digital : f32, + cam_zoom_scroll_delta_scale : f32, + + engine_refresh_hz : uint, + + timing_fps_moving_avg_alpha : f32, + + ui_resize_border_width : f32, + + // color_theme : AppColorTheme, + + text_snap_glyph_shape_position : b32, + text_snap_glyph_render_height : b32, + text_size_screen_scalar : f32, + text_size_canvas_scalar : f32, + text_alpha_sharpen : f32, +} + +FrameTime :: struct { + sleep_is_granular : b32, + + current_frame : u64, + delta_seconds : f64, + delta_ms : f64, + delta_ns : Duration, + target_ms : f64, + elapsed_ms : f64, + avg_ms : f64, + fps_avg : f64, +} + +State :: struct { + config: AppConfig, + + // Overall frametime of the tick frame (currently main thread's) + using frametime : FrameTime, +} + +ThreadState :: struct { + delta_seconds: f64, + delta_ms: f64, + delta_ns: Duration, + elapsed_ms: f64, + avg_ms: f64, +} + +app_config :: #force_inline proc "contextless" () -> AppConfig { return memory.client_memory.config } +get_frametime :: #force_inline proc "contextless" () -> FrameTime { return memory.client_memory.frametime }