From d7ecdfb950c3425df33d57f6e29f2132dde1f532 Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 3 Apr 2026 11:42:25 -0500 Subject: [PATCH] box-targeted UI debug break cmd --- src/gpu/gpu_dx12/gpu_dx12_core.h | 2 +- src/pp/pp.c | 2707 +++++++++++++++--------------- src/pp/pp_sim/pp_sim_core.c | 2 + src/pp/pp_vis/pp_vis_core.c | 1930 +++++++++++---------- src/pp/pp_vis/pp_vis_core.h | 1 + src/pp/pp_vis/pp_vis_gpu.g | 3 +- src/ui/ui_core.c | 832 ++++----- src/ui/ui_core.h | 16 +- 8 files changed, 2857 insertions(+), 2636 deletions(-) diff --git a/src/gpu/gpu_dx12/gpu_dx12_core.h b/src/gpu/gpu_dx12/gpu_dx12_core.h index 60402346..83232a81 100644 --- a/src/gpu/gpu_dx12/gpu_dx12_core.h +++ b/src/gpu/gpu_dx12/gpu_dx12_core.h @@ -14,7 +14,7 @@ //~ Tweakable definitions #define G_D12_TearingIsAllowed 1 -#define G_D12_FrameLatency 1 +#define G_D12_FrameLatency 2 #define G_D12_SwapchainBufferCount 2 #define G_D12_SwapchainFlags ( \ ((G_D12_TearingIsAllowed != 0) * DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) | \ diff --git a/src/pp/pp.c b/src/pp/pp.c index d1600064..53f3f3d9 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -2163,627 +2163,631 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick) void P_StepFrame(P_Frame *frame) { TempArena scratch = BeginScratchNoConflict(); - P_World *world = frame->world; - P_Frame *prev_frame = frame->prev; - - i64 sim_dt_ns = SIM_TICK_INTERVAL_NS; - f64 sim_dt = SecondsFromNs(sim_dt_ns); - - ////////////////////////////// - //- Kill ents with expired lifetimes + ProfZoneDF("Simulate world") { - for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - { - if (ent->lifetime_seconds != Inf && SecondsFromNs(frame->time_ns - ent->created_at_ns) >= ent->lifetime_seconds) - { - ent->exists = 0; - } - } - } + P_World *world = frame->world; + P_Frame *prev_frame = frame->prev; - ////////////////////////////// - //- Prune ents + i64 sim_dt_ns = SIM_TICK_INTERVAL_NS; + f64 sim_dt = SecondsFromNs(sim_dt_ns); + + ////////////////////////////// + //- Kill ents with expired lifetimes - { - // Mark dead child ents - for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { - if (ent->exists <= 0) + for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { - P_Ent *weapon = P_EntFromKey(frame, ent->weapon); - if (!P_IsEntNil(weapon)) + if (ent->lifetime_seconds != Inf && SecondsFromNs(frame->time_ns - ent->created_at_ns) >= ent->lifetime_seconds) { - weapon->exists = 0; + ent->exists = 0; } } } - // Gather ents to prune - i64 ents_to_prune_count = 0; - P_EntKey *ents_to_prune = PushStructsNoZero(scratch.arena, P_EntKey, frame->ents_count); - for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + ////////////////////////////// + //- Prune ents + { - if (ent->exists <= 0) + // Mark dead child ents + for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { - ents_to_prune[ents_to_prune_count++] = ent->key; - } - } - - // Prune - for (i64 prune_idx = 0; prune_idx < ents_to_prune_count; ++prune_idx) - { - // FIXME: Ensure sure prunes are received by clients - P_EntKey key = ents_to_prune[prune_idx]; - P_Ent *ent = P_EntFromKey(frame, key); - if (!P_IsEntNil(ent)) - { - P_EntBin *bin = &frame->ent_bins[ent->key.v % frame->ent_bins_count]; - DllQueueRemoveNP(bin->first, bin->last, ent, next_in_bin, prev_in_bin); - DllQueueRemoveNPZ(&P_NilEnt, frame->first_ent, frame->last_ent, ent, next, prev); - frame->ents_count -= 1; - SllStackPush(world->first_free_ent, ent); - } - } - } - - ////////////////////////////// - //- Query ents - - b32 is_client = P_tl.is_client; - P_Ent *local_player = P_EntFromKey(frame, P_tl.local_player); - P_Ent *local_guy = P_EntFromKey(frame, local_player->guy); - - ////////////////////////////// - //- Mark simulated ents - - if (is_client) - { - P_EntKey local_player_key = local_player->key; - P_EntKey local_guy_key = local_guy->key; - P_EntKey local_weapon_key = local_guy->weapon; - - // Mark player/guy/weapon ents - for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - { - P_EntKey key = ent->key; - b32 sim = 0; - if ( - P_MatchEntKey(key, local_player_key) || - P_MatchEntKey(key, local_guy_key) || - P_MatchEntKey(key, local_weapon_key) - ) - { - sim = 1; - } - ent->sim = sim; - } - - // Mark child ents - for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - { - if (!ent->sim) - { - ent->sim = P_EntFromKey(frame, ent->source)->sim; - } - } - } - - ////////////////////////////// - //- Spawn pickups - - if (!is_client) - { - P_EntList queued_ents = Zi; - for (P_Ent *spawner = P_FirstEnt(frame); !P_IsEntNil(spawner); spawner = P_NextEnt(spawner)) - { - b32 is_spawner = spawner->is_health_spawn; - if (is_spawner) - { - if (!P_IsEntKeyNil(spawner->pickup)) + if (ent->exists <= 0) { - P_Ent *pickup = P_EntFromKey(frame, spawner->pickup); - if (P_IsEntNil(pickup)) + P_Ent *weapon = P_EntFromKey(frame, ent->weapon); + if (!P_IsEntNil(weapon)) + { + weapon->exists = 0; + } + } + } + + // Gather ents to prune + i64 ents_to_prune_count = 0; + P_EntKey *ents_to_prune = PushStructsNoZero(scratch.arena, P_EntKey, frame->ents_count); + for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + if (ent->exists <= 0) + { + ents_to_prune[ents_to_prune_count++] = ent->key; + } + } + + // Prune + for (i64 prune_idx = 0; prune_idx < ents_to_prune_count; ++prune_idx) + { + // FIXME: Ensure sure prunes are received by clients + P_EntKey key = ents_to_prune[prune_idx]; + P_Ent *ent = P_EntFromKey(frame, key); + if (!P_IsEntNil(ent)) + { + P_EntBin *bin = &frame->ent_bins[ent->key.v % frame->ent_bins_count]; + DllQueueRemoveNP(bin->first, bin->last, ent, next_in_bin, prev_in_bin); + DllQueueRemoveNPZ(&P_NilEnt, frame->first_ent, frame->last_ent, ent, next, prev); + frame->ents_count -= 1; + SllStackPush(world->first_free_ent, ent); + } + } + } + + ////////////////////////////// + //- Query ents + + b32 is_client = P_tl.is_client; + P_Ent *local_player = P_EntFromKey(frame, P_tl.local_player); + P_Ent *local_guy = P_EntFromKey(frame, local_player->guy); + + ////////////////////////////// + //- Mark simulated ents + + if (is_client) + { + P_EntKey local_player_key = local_player->key; + P_EntKey local_guy_key = local_guy->key; + P_EntKey local_weapon_key = local_guy->weapon; + + // Mark player/guy/weapon ents + for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + P_EntKey key = ent->key; + b32 sim = 0; + if ( + P_MatchEntKey(key, local_player_key) || + P_MatchEntKey(key, local_guy_key) || + P_MatchEntKey(key, local_weapon_key) + ) + { + sim = 1; + } + ent->sim = sim; + } + + // Mark child ents + for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + if (!ent->sim) + { + ent->sim = P_EntFromKey(frame, ent->source)->sim; + } + } + } + + ////////////////////////////// + //- Spawn pickups + + if (!is_client) + { + P_EntList queued_ents = Zi; + for (P_Ent *spawner = P_FirstEnt(frame); !P_IsEntNil(spawner); spawner = P_NextEnt(spawner)) + { + b32 is_spawner = spawner->is_health_spawn; + if (is_spawner) + { + if (!P_IsEntKeyNil(spawner->pickup)) + { + P_Ent *pickup = P_EntFromKey(frame, spawner->pickup); + if (P_IsEntNil(pickup)) + { + spawner->last_spawn_reset_ns = frame->time_ns; + spawner->pickup = P_NilEntKey; + } + } + } + if (is_spawner && P_IsEntKeyNil(spawner->pickup)) + { + i64 spawn_interval_ns = 0; + if (spawner->is_health_spawn) + { + spawn_interval_ns = NsFromSeconds(1); + } + b32 should_spawn = spawner->last_spawn_reset_ns == 0 || spawner->last_spawn_reset_ns + spawn_interval_ns <= frame->time_ns; + if (should_spawn) { spawner->last_spawn_reset_ns = frame->time_ns; - spawner->pickup = P_NilEntKey; - } - } - } - if (is_spawner && P_IsEntKeyNil(spawner->pickup)) - { - i64 spawn_interval_ns = 0; - if (spawner->is_health_spawn) - { - spawn_interval_ns = NsFromSeconds(1); - } - b32 should_spawn = spawner->last_spawn_reset_ns == 0 || spawner->last_spawn_reset_ns + spawn_interval_ns <= frame->time_ns; - if (should_spawn) - { - spawner->last_spawn_reset_ns = frame->time_ns; - P_Ent *pickup = P_PushTempEnt(scratch.arena, &queued_ents); - { - pickup->key = P_RandEntKey(); - pickup->xf = spawner->xf; - pickup->is_pickup = 1; - pickup->source = spawner->key; - pickup->is_health = spawner->is_health_spawn; - } - spawner->pickup = pickup->key; - } - } - } - P_SpawnEntsFromList(frame, queued_ents); - } - - ////////////////////////////// - //- Spawn guys - - if (!is_client) - { - P_EntList queued_ents = Zi; - for (P_Ent *player = P_FirstEnt(frame); !P_IsEntNil(player); player = P_NextEnt(player)) - { - if (player->is_player) - { - if (P_IsEntKeyNil(player->guy)) - { - player->guy = P_RandEntKey(); - } - P_Ent *guy = P_EntFromKey(frame, player->guy); - if (P_IsEntNil(guy)) - { - guy = P_PushTempEnt(scratch.arena, &queued_ents); - guy->is_guy = 1; - guy->key = player->guy; - guy->source = player->key; - - //- Choose guy spawn point - { - P_Ent *highest_scoring_spawn = &P_NilEnt; + P_Ent *pickup = P_PushTempEnt(scratch.arena, &queued_ents); { - Struct(SpawnNode) - { - SpawnNode *next; - P_Ent *ent; - f32 score; - }; + pickup->key = P_RandEntKey(); + pickup->xf = spawner->xf; + pickup->is_pickup = 1; + pickup->source = spawner->key; + pickup->is_health = spawner->is_health_spawn; + } + spawner->pickup = pickup->key; + } + } + } + P_SpawnEntsFromList(frame, queued_ents); + } - i64 spawns_count = 0; - SpawnNode *first_spawn = 0; - SpawnNode *last_spawn = 0; + ////////////////////////////// + //- Spawn guys - // Push spawns - for (P_Ent *spawn_ent = P_FirstEnt(frame); !P_IsEntNil(spawn_ent); spawn_ent = P_NextEnt(spawn_ent)) - { - if (spawn_ent->is_guy_spawn) - { - SpawnNode *spawn = PushStruct(scratch.arena, SpawnNode); - SllQueuePush(first_spawn, last_spawn, spawn); - spawn->ent = spawn_ent; - spawn->score = P_WorldPitch * 1000; - ++spawns_count; - } - } + if (!is_client) + { + P_EntList queued_ents = Zi; + for (P_Ent *player = P_FirstEnt(frame); !P_IsEntNil(player); player = P_NextEnt(player)) + { + if (player->is_player) + { + if (P_IsEntKeyNil(player->guy)) + { + player->guy = P_RandEntKey(); + } + P_Ent *guy = P_EntFromKey(frame, player->guy); + if (P_IsEntNil(guy)) + { + guy = P_PushTempEnt(scratch.arena, &queued_ents); + guy->is_guy = 1; + guy->key = player->guy; + guy->source = player->key; - // Score spawns - for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + //- Choose guy spawn point + { + P_Ent *highest_scoring_spawn = &P_NilEnt; { - b32 should_avoid = 0; - if (ent->is_guy) + Struct(SpawnNode) { - should_avoid = 1; - } - if (P_MatchEntKey(ent->key, player->spawn)) + SpawnNode *next; + P_Ent *ent; + f32 score; + }; + + i64 spawns_count = 0; + SpawnNode *first_spawn = 0; + SpawnNode *last_spawn = 0; + + // Push spawns + for (P_Ent *spawn_ent = P_FirstEnt(frame); !P_IsEntNil(spawn_ent); spawn_ent = P_NextEnt(spawn_ent)) { - // Avoid old spawn - should_avoid = 1; - } - if (should_avoid) - { - for (SpawnNode *spawn = first_spawn; spawn; spawn = spawn->next) + if (spawn_ent->is_guy_spawn) { - // TODO: Something better than linear distance for scoring - f32 score = Vec2Len(SubVec2(ent->xf.t, spawn->ent->xf.t)); - spawn->score = MinF32(spawn->score, score); + SpawnNode *spawn = PushStruct(scratch.arena, SpawnNode); + SllQueuePush(first_spawn, last_spawn, spawn); + spawn->ent = spawn_ent; + spawn->score = P_WorldPitch * 1000; + ++spawns_count; + } + } + + // Score spawns + for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + b32 should_avoid = 0; + if (ent->is_guy) + { + should_avoid = 1; + } + if (P_MatchEntKey(ent->key, player->spawn)) + { + // Avoid old spawn + should_avoid = 1; + } + if (should_avoid) + { + for (SpawnNode *spawn = first_spawn; spawn; spawn = spawn->next) + { + // TODO: Something better than linear distance for scoring + f32 score = Vec2Len(SubVec2(ent->xf.t, spawn->ent->xf.t)); + spawn->score = MinF32(spawn->score, score); + } + } + } + + // Find highest scoring spawn + i64 highest_score = -Inf; + for (SpawnNode *spawn = first_spawn; spawn; spawn = spawn->next) + { + f32 rand_score_spread = 10; + f32 virtual_score = spawn->score + rand_score_spread * Norm24(RandU64FromState(&world->rand)); + if (virtual_score > highest_score) + { + highest_score = virtual_score; + highest_scoring_spawn = spawn->ent; } } } - - // Find highest scoring spawn - i64 highest_score = -Inf; - for (SpawnNode *spawn = first_spawn; spawn; spawn = spawn->next) - { - f32 rand_score_spread = 10; - f32 virtual_score = spawn->score + rand_score_spread * Norm24(RandU64FromState(&world->rand)); - if (virtual_score > highest_score) - { - highest_score = virtual_score; - highest_scoring_spawn = spawn->ent; - } - } + guy->xf = highest_scoring_spawn->xf; + player->spawn = highest_scoring_spawn->key; } - guy->xf = highest_scoring_spawn->xf; - player->spawn = highest_scoring_spawn->key; } } } + P_SpawnEntsFromList(frame, queued_ents); } - P_SpawnEntsFromList(frame, queued_ents); - } - ////////////////////////////// - //- Equip spawned guys + ////////////////////////////// + //- Equip spawned guys - // TODO: Remove this (weapon testing) + // TODO: Remove this (weapon testing) - { - P_EntList queued_ents = Zi; - for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) { - if (guy->is_guy && guy->created_at_tick == frame->tick) + P_EntList queued_ents = Zi; + for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) { - P_Ent *weapon = P_EntFromKey(frame, guy->weapon); - if (!weapon->is_weapon) + if (guy->is_guy && guy->created_at_tick == frame->tick) { - weapon = P_PushTempEnt(scratch.arena, &queued_ents); - weapon->is_weapon = 1; - weapon->key = P_RandEntKey(); - weapon->source = guy->key; - weapon->is_uzi = 1; - // weapon->is_launcher = 1; - guy->weapon = weapon->key; + P_Ent *weapon = P_EntFromKey(frame, guy->weapon); + if (!weapon->is_weapon) + { + weapon = P_PushTempEnt(scratch.arena, &queued_ents); + weapon->is_weapon = 1; + weapon->key = P_RandEntKey(); + weapon->source = guy->key; + weapon->is_uzi = 1; + // weapon->is_launcher = 1; + guy->weapon = weapon->key; + } } } + P_SpawnEntsFromList(frame, queued_ents); } - P_SpawnEntsFromList(frame, queued_ents); - } - ////////////////////////////// - //- Update guy controls from player controls + ////////////////////////////// + //- Update guy controls from player controls + + { + for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) + { + if (guy->is_guy) + { + ZeroStruct(&guy->control); + } + } + + for (P_Ent *player = P_FirstEnt(frame); !P_IsEntNil(player); player = P_NextEnt(player)) + { + if (player->is_player) + { + P_Ent *guy = P_EntFromKey(frame, player->guy); + if (!P_IsEntNil(guy)) + { + guy->control = player->control; + } + } + } + + // Normalize controls + for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + ent->control.move = ClampVec2Len(ent->control.move, 0, 1); + } + } + + ////////////////////////////// + //- Roll guys - { for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) { if (guy->is_guy) { - ZeroStruct(&guy->control); - } - } - - for (P_Ent *player = P_FirstEnt(frame); !P_IsEntNil(player); player = P_NextEnt(player)) - { - if (player->is_player) - { - P_Ent *guy = P_EntFromKey(frame, player->guy); - if (!P_IsEntNil(guy)) + // if (guy->control.roll_presses && !IsVec2Zero(guy->control.move)) + if (guy->control.roll_presses) { - guy->control = player->control; - } - } - } + // TODO: Not like this - // Normalize controls - for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - { - ent->control.move = ClampVec2Len(ent->control.move, 0, 1); - } - } + i64 roll_timeout_ns = P_RollTimeoutNs; + i64 roll_time_ns = P_RollTimeNs; - ////////////////////////////// - //- Roll guys - - for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) - { - if (guy->is_guy) - { - // if (guy->control.roll_presses && !IsVec2Zero(guy->control.move)) - if (guy->control.roll_presses) - { - // TODO: Not like this - - i64 roll_timeout_ns = P_RollTimeoutNs; - i64 roll_time_ns = P_RollTimeNs; - - if (frame->time_ns - roll_timeout_ns - roll_time_ns > guy->last_roll_time_ns || guy->last_roll_time_ns == 0) - { - guy->last_roll_time_ns = frame->time_ns; - // guy->last_roll_dir = NormVec2(guy->control.move); - guy->last_roll_dir = NormVec2(guy->control.look); - } - } - - if (!P_IsEntRolling(frame, guy)) - { - b32 is_moving = Vec2LenSq(guy->control.move) > (0.001 * 0.001); - if (is_moving) - { - guy->walk_time_accum_ns += sim_dt_ns; - } - } - } - } - - ////////////////////////////// - //- Integrate guy control forces - - for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) - { - if (guy->is_guy && guy->sim) - { - P_Control control = guy->control; - - // Dampen movement - { - if (Vec2Len(guy->v) > 0.001) - { - f32 damp_force = TweakFloat("Guy damp force", 50, 0, 100); - // f32 damp_force = TweakFloat("Guy damp force", 0, 0, 100); - Vec2 damp = MulVec2(NegVec2(guy->v), damp_force * sim_dt); - guy->v = AddVec2(guy->v, damp); - } - else - { - guy->v = VEC2(0, 0); - } - } - - f32 turn_rate = TweakFloat("Guy turn rate", 1, 0, 1); - f32 move_force = TweakFloat("Guy move force", 400, 0, 400); - f32 max_speed = TweakFloat("Guy max speed", 10, 0, 20); - - Vec2 look = control.look; - Vec2 move = control.move; - - // FIXME: Roll timeout - b32 is_rolling = P_IsEntRolling(frame, guy); - if (is_rolling) - { - // Vec2 roll_dir = NormVec2(guy->last_roll_dir); - Vec2 roll_dir = NormVec2(guy->xf.r); - - move = roll_dir; - // look = roll_dir; - - f32 roll_factor = 1.3; - move_force *= roll_factor; - max_speed *= roll_factor; - - // if ((frame->time_ns - guy->last_roll_time_ns) > P_RollTurnTimeNs) - { - turn_rate = 0.1; - } - } - - // Integrate linear movement - { - Vec2 new_velocity = guy->v; - new_velocity = AddVec2(new_velocity, MulVec2(move, move_force * sim_dt)); - if (Vec2Len(new_velocity) > max_speed) - { - new_velocity = Vec2WithLen(new_velocity, max_speed); - } - - guy->v = new_velocity; - } - - // Integrate look - { - f32 cur_angle = AngleFromVec2(guy->xf.r); - f32 desired_angle = AngleFromVec2(look); - f32 diff = UnwindAngleF32(desired_angle - cur_angle); - f32 look_force = 1.0 / (sim_dt * sim_dt) * turn_rate; - guy->w = diff * sim_dt * look_force; - } - } - } - - ////////////////////////////// - //- Bake world - - { - u64 desired_bake_hash = world->tiles_hash; - if (desired_bake_hash != world->baked_hash) - { - LogDebugF("Bake step"); - ResetArena(world->bake_arena); - world->walls_space = P_SpaceFromWalls(world->bake_arena, frame); - world->baked_hash = desired_bake_hash; - } - } - - ////////////////////////////// - //- Build pre-solve space from ents - - P_Space pre_solve_ents_space = P_SpaceFromEnts(scratch.arena, frame); - - ////////////////////////////// - //- Setup constraint constants - - i32 solver_steps_count = SIM_PHYSICS_SUBSTEPS; - f32 solver_dt = sim_dt / solver_steps_count; - - // Solid params - // SoftSpring solid_spring = MakeSpring(TweakFloat("Contact spring hz", 25, 5, 200), TweakFloat("Contact spring damp", 10, 5, 100), solver_dt); - SoftSpring solid_spring = MakeSpring(TweakFloat("Contact spring hz", 100, 5, 200), TweakFloat("Contact spring damp", 10, 5, 100), solver_dt); - f32 solid_pushout_velocity = TweakFloat("Contact spring pushout", 3, 0, 50); - - // Gentle params - // f32 gentle_pushout_factor = TweakFloat("Gentle pushout factor", 10, 0, 50); - f32 gentle_pushout_factor = TweakFloat("Gentle pushout factor", 0.5, 0, 50); - - ////////////////////////////// - //- Generate guy constraints - - for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0)) - { - if (ent0->is_guy) - { - P_Shape shape0 = P_WorldShapeFromEnt(ent0); - Rng2 aabb0 = P_BoundingBoxFromShape(shape0); - - Rng2 query_rect = Zi; - query_rect.p0 = FloorVec2(aabb0.p0); - query_rect.p1 = CeilVec2(aabb0.p1); - query_rect.p1.x = MaxF32(query_rect.p1.x, query_rect.p0.x + 1); - query_rect.p1.y = MaxF32(query_rect.p1.y, query_rect.p0.y + 1); - - for (i32 query_y = query_rect.p0.y; query_y < query_rect.p1.y; ++query_y) - { - for (i32 query_x = query_rect.p0.x; query_x < query_rect.p1.x; ++query_x) - { - P_SpaceCell cells[] = { - P_SpaceCellFromPos(&pre_solve_ents_space, VEC2(query_x, query_y)), - P_SpaceCellFromPos(&world->walls_space, VEC2(query_x, query_y)), - }; - for (i64 cell_idx = 0; cell_idx < countof(cells); ++cell_idx) + if (frame->time_ns - roll_timeout_ns - roll_time_ns > guy->last_roll_time_ns || guy->last_roll_time_ns == 0) { - P_SpaceCell cell = cells[cell_idx]; - for (P_SpaceEntryNode *space_entry_node = cell.first; space_entry_node; space_entry_node = space_entry_node->next) + guy->last_roll_time_ns = frame->time_ns; + // guy->last_roll_dir = NormVec2(guy->control.move); + guy->last_roll_dir = NormVec2(guy->control.look); + } + } + + if (!P_IsEntRolling(frame, guy)) + { + b32 is_moving = Vec2LenSq(guy->control.move) > (0.001 * 0.001); + if (is_moving) + { + guy->walk_time_accum_ns += sim_dt_ns; + } + } + } + } + + ////////////////////////////// + //- Integrate guy control forces + + for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) + { + if (guy->is_guy && guy->sim) + { + P_Control control = guy->control; + + // Dampen movement + { + if (Vec2Len(guy->v) > 0.001) + { + f32 damp_force = TweakFloat("Guy damp force", 50, 0, 100); + // f32 damp_force = TweakFloat("Guy damp force", 0, 0, 100); + Vec2 damp = MulVec2(NegVec2(guy->v), damp_force * sim_dt); + guy->v = AddVec2(guy->v, damp); + } + else + { + guy->v = VEC2(0, 0); + } + } + + f32 turn_rate = TweakFloat("Guy turn rate", 1, 0, 1); + f32 move_force = TweakFloat("Guy move force", 400, 0, 400); + f32 max_speed = TweakFloat("Guy max speed", 10, 0, 20); + + Vec2 look = control.look; + Vec2 move = control.move; + + // FIXME: Roll timeout + b32 is_rolling = P_IsEntRolling(frame, guy); + if (is_rolling) + { + // Vec2 roll_dir = NormVec2(guy->last_roll_dir); + Vec2 roll_dir = NormVec2(guy->xf.r); + + move = roll_dir; + // look = roll_dir; + + f32 roll_factor = 1.3; + move_force *= roll_factor; + max_speed *= roll_factor; + + // if ((frame->time_ns - guy->last_roll_time_ns) > P_RollTurnTimeNs) + { + turn_rate = 0.1; + } + } + + // Integrate linear movement + { + Vec2 new_velocity = guy->v; + new_velocity = AddVec2(new_velocity, MulVec2(move, move_force * sim_dt)); + if (Vec2Len(new_velocity) > max_speed) + { + new_velocity = Vec2WithLen(new_velocity, max_speed); + } + + guy->v = new_velocity; + } + + // Integrate look + { + f32 cur_angle = AngleFromVec2(guy->xf.r); + f32 desired_angle = AngleFromVec2(look); + f32 diff = UnwindAngleF32(desired_angle - cur_angle); + f32 look_force = 1.0 / (sim_dt * sim_dt) * turn_rate; + guy->w = diff * sim_dt * look_force; + } + } + } + + ////////////////////////////// + //- Bake world + + { + u64 desired_bake_hash = world->tiles_hash; + if (desired_bake_hash != world->baked_hash) + { + LogDebugF("Bake step"); + ResetArena(world->bake_arena); + world->walls_space = P_SpaceFromWalls(world->bake_arena, frame); + world->baked_hash = desired_bake_hash; + } + } + + ////////////////////////////// + //- Build pre-solve space from ents + + P_Space pre_solve_ents_space = P_SpaceFromEnts(scratch.arena, frame); + + ////////////////////////////// + //- Setup constraint constants + + i32 solver_steps_count = SIM_PHYSICS_SUBSTEPS; + f32 solver_dt = sim_dt / solver_steps_count; + + // Solid params + // SoftSpring solid_spring = MakeSpring(TweakFloat("Contact spring hz", 25, 5, 200), TweakFloat("Contact spring damp", 10, 5, 100), solver_dt); + SoftSpring solid_spring = MakeSpring(TweakFloat("Contact spring hz", 100, 5, 200), TweakFloat("Contact spring damp", 10, 5, 100), solver_dt); + f32 solid_pushout_velocity = TweakFloat("Contact spring pushout", 3, 0, 50); + + // Gentle params + // f32 gentle_pushout_factor = TweakFloat("Gentle pushout factor", 10, 0, 50); + f32 gentle_pushout_factor = TweakFloat("Gentle pushout factor", 0.5, 0, 50); + + ////////////////////////////// + //- Generate guy constraints + + for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0)) + { + if (ent0->is_guy) + { + P_Shape shape0 = P_WorldShapeFromEnt(ent0); + Rng2 aabb0 = P_BoundingBoxFromShape(shape0); + + Rng2 query_rect = Zi; + query_rect.p0 = FloorVec2(aabb0.p0); + query_rect.p1 = CeilVec2(aabb0.p1); + query_rect.p1.x = MaxF32(query_rect.p1.x, query_rect.p0.x + 1); + query_rect.p1.y = MaxF32(query_rect.p1.y, query_rect.p0.y + 1); + + for (i32 query_y = query_rect.p0.y; query_y < query_rect.p1.y; ++query_y) + { + for (i32 query_x = query_rect.p0.x; query_x < query_rect.p1.x; ++query_x) + { + P_SpaceCell cells[] = { + P_SpaceCellFromPos(&pre_solve_ents_space, VEC2(query_x, query_y)), + P_SpaceCellFromPos(&world->walls_space, VEC2(query_x, query_y)), + }; + for (i64 cell_idx = 0; cell_idx < countof(cells); ++cell_idx) { - P_SpaceEntry *space_entry = &space_entry_node->entry; - Rng2 aabb1 = P_BoundingBoxFromShape(space_entry->shape); - P_Ent *ent1 = P_EntFromKey(frame, (P_EntKey) { .v = space_entry->shape_id }); - if (!P_MatchEntKey(ent0->key, ent1->key) && IsIntersectingRng2(aabb0, aabb1)) + P_SpaceCell cell = cells[cell_idx]; + for (P_SpaceEntryNode *space_entry_node = cell.first; space_entry_node; space_entry_node = space_entry_node->next) { - P_Shape shape1 = space_entry->shape; - b32 is_static_collision = P_IsEntNil(ent1); - b32 is_guy_on_guy_collision = ent0->is_guy && ent1->is_guy; - - if (is_static_collision || is_guy_on_guy_collision) + P_SpaceEntry *space_entry = &space_entry_node->entry; + Rng2 aabb1 = P_BoundingBoxFromShape(space_entry->shape); + P_Ent *ent1 = P_EntFromKey(frame, (P_EntKey) { .v = space_entry->shape_id }); + if (!P_MatchEntKey(ent0->key, ent1->key) && IsIntersectingRng2(aabb0, aabb1)) { - P_ConstraintKey constraint_key = Zi; + P_Shape shape1 = space_entry->shape; + b32 is_static_collision = P_IsEntNil(ent1); + b32 is_guy_on_guy_collision = ent0->is_guy && ent1->is_guy; + + if (is_static_collision || is_guy_on_guy_collision) { - // Deterministic shape ID order for consistent constraint lookup - u64 shape_id0 = ent0->key.v; - u64 shape_id1 = space_entry->shape_id; - if (shape_id0 > shape_id1) + P_ConstraintKey constraint_key = Zi; { - u64 tmp = shape_id0; - shape_id0 = shape_id1; - shape_id1 = tmp; + // Deterministic shape ID order for consistent constraint lookup + u64 shape_id0 = ent0->key.v; + u64 shape_id1 = space_entry->shape_id; + if (shape_id0 > shape_id1) + { + u64 tmp = shape_id0; + shape_id0 = shape_id1; + shape_id1 = tmp; + } + constraint_key = P_ConstraintKeyFromU64s(shape_id0, shape_id1); } - constraint_key = P_ConstraintKeyFromU64s(shape_id0, shape_id1); - } - P_Constraint *constraint = P_ConstraintFromKey(frame, constraint_key); - if (constraint->last_touched_tick < frame->tick) - { - P_CollisionResult collision = P_CollisionResultFromShapes(shape0, shape1); - - b32 skip_collision = 0; - skip_collision = skip_collision || collision.collision_points_count <= 0; - if (!skip_collision && !IsVec2Zero(space_entry->dir)) + P_Constraint *constraint = P_ConstraintFromKey(frame, constraint_key); + if (constraint->last_touched_tick < frame->tick) { - // Skip collision if normal violates one-way direction - // f32 threshold = 0.5; - f32 threshold = 0; - skip_collision = DotVec2(space_entry->dir, collision.collision_normal) >= threshold; - } + P_CollisionResult collision = P_CollisionResultFromShapes(shape0, shape1); - if (!skip_collision) - { - if (P_IsConstraintNil(constraint)) + b32 skip_collision = 0; + skip_collision = skip_collision || collision.collision_points_count <= 0; + if (!skip_collision && !IsVec2Zero(space_entry->dir)) { - constraint = P_PushConstraint(frame, constraint_key); - } - constraint->last_touched_tick = frame->tick; - constraint->normal = collision.collision_normal; - // constraint->friction = SqrtF32(ent0->friction * ent1->friction); - constraint->friction = 0; - - if (is_static_collision) - { - constraint->flags |= P_ConstraintFlag_Solid; - } - else if (is_guy_on_guy_collision) - { - constraint->flags |= P_ConstraintFlag_Gentle; - // constraint->flags |= P_ConstraintFlag_NoWarmStart; + // Skip collision if normal violates one-way direction + // f32 threshold = 0.5; + f32 threshold = 0; + skip_collision = DotVec2(space_entry->dir, collision.collision_normal) >= threshold; } - // TODO: Real masses - f32 inv_m0 = 10; - f32 inv_m1 = 10; - f32 inv_i0 = 0; - f32 inv_i1 = 0; - - // Treat statics / non-predicted ents as infinite-mass - if (!ent0->is_guy || !ent0->sim) + if (!skip_collision) { - inv_m0 = 0; - inv_i0 = 0; - } - if (!ent1->is_guy || !ent1->sim) - { - inv_m1 = 0; - inv_i1 = 0; - } - - constraint->ent0 = ent0->key; - constraint->ent1 = ent1->key; - constraint->static_center0 = shape0.center_of_mass; - constraint->static_center1 = shape1.center_of_mass; - - constraint->inv_m0 = inv_m0; - constraint->inv_m1 = inv_m1; - constraint->inv_i0 = inv_i0; - constraint->inv_i1 = inv_i1; - - // Delete old contacts that are no longer present - for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx) - { - P_ContactPoint *contact = &constraint->points[contact_point_idx]; - u32 id = contact->id; - b32 match = 0; - for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx) + if (P_IsConstraintNil(constraint)) { - if (collision.collision_points[collision_point_idx].id == id) - { - match = 1; - break; - } + constraint = P_PushConstraint(frame, constraint_key); } - if (!match) + constraint->last_touched_tick = frame->tick; + constraint->normal = collision.collision_normal; + // constraint->friction = SqrtF32(ent0->friction * ent1->friction); + constraint->friction = 0; + + if (is_static_collision) { - // Delete contact by replacing with last in array - *contact = constraint->points[constraint->points_count - 1]; - constraint->points_count -= 1; - contact_point_idx -= 1; + constraint->flags |= P_ConstraintFlag_Solid; } - } - - // Create / update contacts from collision - for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx) - { - P_CollisionPoint collision_point = collision.collision_points[collision_point_idx]; - - u32 id = collision_point.id; - P_ContactPoint *contact = 0; + else if (is_guy_on_guy_collision) { - for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx) + constraint->flags |= P_ConstraintFlag_Gentle; + // constraint->flags |= P_ConstraintFlag_NoWarmStart; + } + + // TODO: Real masses + f32 inv_m0 = 10; + f32 inv_m1 = 10; + f32 inv_i0 = 0; + f32 inv_i1 = 0; + + // Treat statics / non-predicted ents as infinite-mass + if (!ent0->is_guy || !ent0->sim) + { + inv_m0 = 0; + inv_i0 = 0; + } + if (!ent1->is_guy || !ent1->sim) + { + inv_m1 = 0; + inv_i1 = 0; + } + + constraint->ent0 = ent0->key; + constraint->ent1 = ent1->key; + constraint->static_center0 = shape0.center_of_mass; + constraint->static_center1 = shape1.center_of_mass; + + constraint->inv_m0 = inv_m0; + constraint->inv_m1 = inv_m1; + constraint->inv_i0 = inv_i0; + constraint->inv_i1 = inv_i1; + + // Delete old contacts that are no longer present + for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx) + { + P_ContactPoint *contact = &constraint->points[contact_point_idx]; + u32 id = contact->id; + b32 match = 0; + for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx) { - P_ContactPoint *tmp = &constraint->points[contact_point_idx]; - if (tmp->id == id) + if (collision.collision_points[collision_point_idx].id == id) { - contact = tmp; + match = 1; break; } } - if (!contact) + if (!match) { - contact = &constraint->points[constraint->points_count]; - constraint->points_count += 1; - ZeroStruct(contact); + // Delete contact by replacing with last in array + *contact = constraint->points[constraint->points_count - 1]; + constraint->points_count -= 1; + contact_point_idx -= 1; } } - contact->id = id; - Vec2 vcp0 = SubVec2(collision_point.p, shape0.center_of_mass); - Vec2 vcp1 = SubVec2(collision_point.p, shape1.center_of_mass); + // Create / update contacts from collision + for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx) + { + P_CollisionPoint collision_point = collision.collision_points[collision_point_idx]; - contact->vcp0 = vcp0; - contact->vcp1 = vcp1; - contact->starting_separation = collision_point.separation; + u32 id = collision_point.id; + P_ContactPoint *contact = 0; + { + for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx) + { + P_ContactPoint *tmp = &constraint->points[contact_point_idx]; + if (tmp->id == id) + { + contact = tmp; + break; + } + } + if (!contact) + { + contact = &constraint->points[constraint->points_count]; + constraint->points_count += 1; + ZeroStruct(contact); + } + } + contact->id = id; + + Vec2 vcp0 = SubVec2(collision_point.p, shape0.center_of_mass); + Vec2 vcp1 = SubVec2(collision_point.p, shape1.center_of_mass); + + contact->vcp0 = vcp0; + contact->vcp1 = vcp1; + contact->starting_separation = collision_point.separation; + } } } } @@ -2794,714 +2798,713 @@ void P_StepFrame(P_Frame *frame) } } } - } - ////////////////////////////// - //- Prune constraints - - { - i64 prune_constraints_count = 0; - P_Constraint **prune_constraints = PushStructsNoZero(scratch.arena, P_Constraint *, frame->constraints_count); - for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint)) - { - b32 prune = 1; - if (constraint->last_touched_tick == frame->tick) - { - prune = 0; - } - if (prune) - { - prune_constraints[prune_constraints_count] = constraint; - prune_constraints_count += 1; - } - } - - for (i64 prune_idx = 0; prune_idx < prune_constraints_count; ++prune_idx) - { - P_Constraint *constraint = prune_constraints[prune_idx]; - P_ConstraintBin *bin = &frame->constraint_bins[constraint->key.v % frame->constraint_bins_count]; - DllQueueRemoveNP(bin->first, bin->last, constraint, next_in_bin, prev_in_bin); - DllQueueRemoveNPZ(&P_NilConstraint, frame->first_constraint, frame->last_constraint, constraint, next, prev); - frame->constraints_count -= 1; - SllStackPush(world->first_free_constraint, constraint); - } - } - - ////////////////////////////// - //- Run solver steps - - for (i32 solver_step_idx = 0; solver_step_idx < solver_steps_count; ++solver_step_idx) - { ////////////////////////////// - //- Prepare constraints + //- Prune constraints - for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint)) { - Vec2 normal = constraint->normal; - Vec2 tangent = PerpVec2(normal); - f32 inv_m0 = constraint->inv_m0; - f32 inv_m1 = constraint->inv_m1; - f32 inv_i0 = constraint->inv_i0; - f32 inv_i1 = constraint->inv_i1; - - for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) + i64 prune_constraints_count = 0; + P_Constraint **prune_constraints = PushStructsNoZero(scratch.arena, P_Constraint *, frame->constraints_count); + for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint)) { - P_ContactPoint *contact = &constraint->points[contact_idx]; - Vec2 vcp0 = contact->vcp0; - Vec2 vcp1 = contact->vcp1; - - // Compute normal mass + b32 prune = 1; + if (constraint->last_touched_tick == frame->tick) { - f32 vcp0_wedge = WedgeVec2(vcp0, normal); - f32 vcp1_wedge = WedgeVec2(vcp1, normal); - f32 k = (inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge); - contact->inv_normal_mass = k > 0.0f ? 1.0f / k : 0.0f; + prune = 0; } - - // Compute tangent mass + if (prune) { - f32 vcp0_wedge = WedgeVec2(vcp0, tangent); - f32 vcp1_wedge = WedgeVec2(vcp1, tangent); - f32 k = (inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge); - contact->inv_tangent_mass = k > 0.0f ? 1.0f / k : 0.0f; + prune_constraints[prune_constraints_count] = constraint; + prune_constraints_count += 1; } } + + for (i64 prune_idx = 0; prune_idx < prune_constraints_count; ++prune_idx) + { + P_Constraint *constraint = prune_constraints[prune_idx]; + P_ConstraintBin *bin = &frame->constraint_bins[constraint->key.v % frame->constraint_bins_count]; + DllQueueRemoveNP(bin->first, bin->last, constraint, next_in_bin, prev_in_bin); + DllQueueRemoveNPZ(&P_NilConstraint, frame->first_constraint, frame->last_constraint, constraint, next, prev); + frame->constraints_count -= 1; + SllStackPush(world->first_free_constraint, constraint); + } } ////////////////////////////// - //- Warm start constraints + //- Run solver steps - for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint)) + for (i32 solver_step_idx = 0; solver_step_idx < solver_steps_count; ++solver_step_idx) { - if (!(constraint->flags & P_ConstraintFlag_NoWarmStart)) + ////////////////////////////// + //- Prepare constraints + + for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint)) + { + Vec2 normal = constraint->normal; + Vec2 tangent = PerpVec2(normal); + f32 inv_m0 = constraint->inv_m0; + f32 inv_m1 = constraint->inv_m1; + f32 inv_i0 = constraint->inv_i0; + f32 inv_i1 = constraint->inv_i1; + + for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) + { + P_ContactPoint *contact = &constraint->points[contact_idx]; + Vec2 vcp0 = contact->vcp0; + Vec2 vcp1 = contact->vcp1; + + // Compute normal mass + { + f32 vcp0_wedge = WedgeVec2(vcp0, normal); + f32 vcp1_wedge = WedgeVec2(vcp1, normal); + f32 k = (inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge); + contact->inv_normal_mass = k > 0.0f ? 1.0f / k : 0.0f; + } + + // Compute tangent mass + { + f32 vcp0_wedge = WedgeVec2(vcp0, tangent); + f32 vcp1_wedge = WedgeVec2(vcp1, tangent); + f32 k = (inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge); + contact->inv_tangent_mass = k > 0.0f ? 1.0f / k : 0.0f; + } + } + } + + ////////////////////////////// + //- Warm start constraints + + for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint)) + { + if (!(constraint->flags & P_ConstraintFlag_NoWarmStart)) + { + P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0); + P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); + + Vec2 v0 = ent0->v; + Vec2 v1 = ent1->v; + f32 w0 = ent0->w; + f32 w1 = ent1->w; + + Vec2 normal = constraint->normal; + Vec2 tangent = PerpVec2(normal); + + for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) + { + P_ContactPoint *contact = &constraint->points[contact_idx]; + Vec2 vcp0 = contact->vcp0; + Vec2 vcp1 = contact->vcp1; + + Vec2 impulse = AddVec2(MulVec2(normal, contact->solved_normal_impulse), MulVec2(tangent, contact->solved_tangent_impulse)); + // impulse = MulVec2(impulse, inv_num_points); + + v0 = SubVec2(v0, MulVec2(impulse, constraint->inv_m0)); + v1 = AddVec2(v1, MulVec2(impulse, constraint->inv_m1)); + w0 -= WedgeVec2(vcp0, impulse) * constraint->inv_i0; + w1 += WedgeVec2(vcp1, impulse) * constraint->inv_i1; + } + + if (!P_IsEntNil(ent0)) + { + ent0->v = v0; + ent0->w = w0; + } + if (!P_IsEntNil(ent1)) + { + ent1->v = v1; + ent1->w = w1; + } + } + } + + ////////////////////////////// + //- Solve constraints + + // TODO: Solve wall constraints last + + for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint)) { P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0); P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); + Vec2 normal = constraint->normal; + Vec2 tangent = PerpVec2(normal); + + f32 inv_m0 = constraint->inv_m0; + f32 inv_m1 = constraint->inv_m1; + f32 inv_i0 = constraint->inv_i0; + f32 inv_i1 = constraint->inv_i1; Vec2 v0 = ent0->v; Vec2 v1 = ent1->v; f32 w0 = ent0->w; f32 w1 = ent1->w; - Vec2 normal = constraint->normal; - Vec2 tangent = PerpVec2(normal); - - for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) - { - P_ContactPoint *contact = &constraint->points[contact_idx]; - Vec2 vcp0 = contact->vcp0; - Vec2 vcp1 = contact->vcp1; - - Vec2 impulse = AddVec2(MulVec2(normal, contact->solved_normal_impulse), MulVec2(tangent, contact->solved_tangent_impulse)); - // impulse = MulVec2(impulse, inv_num_points); - - v0 = SubVec2(v0, MulVec2(impulse, constraint->inv_m0)); - v1 = AddVec2(v1, MulVec2(impulse, constraint->inv_m1)); - w0 -= WedgeVec2(vcp0, impulse) * constraint->inv_i0; - w1 += WedgeVec2(vcp1, impulse) * constraint->inv_i1; - } - + Vec2 center0 = constraint->static_center0; + Vec2 center1 = constraint->static_center1; if (!P_IsEntNil(ent0)) { - ent0->v = v0; - ent0->w = w0; + center0 = P_WorldShapeFromEnt(ent0).center_of_mass; } if (!P_IsEntNil(ent1)) { - ent1->v = v1; - ent1->w = w1; + center1 = P_WorldShapeFromEnt(ent1).center_of_mass; + } + + //- Solve solid constraint + if (constraint->flags & P_ConstraintFlag_Solid) + { + // Normal impulse + for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) + { + P_ContactPoint *contact = &constraint->points[contact_idx]; + Vec2 vcp0 = contact->vcp0; + Vec2 vcp1 = contact->vcp1; + Vec2 p0 = AddVec2(center0, vcp0); + Vec2 p1 = AddVec2(center1, vcp1); + f32 separation = DotVec2(SubVec2(p1, p0), normal) + contact->starting_separation; + + f32 velocity_bias = 0.0; + f32 mass_scale = 1.0; + f32 impulse_scale = 0.0; + + // TODO: Do a relaxation pass without bias + b32 apply_bias = 1; + if (separation > 0.0) + { + // Speculative + velocity_bias = separation / solver_dt; + } + else if (apply_bias) + { + // Soft constraint + SoftSpring softness = solid_spring; + f32 pushout_velocity = solid_pushout_velocity; + mass_scale = softness.mass_scale; + impulse_scale = softness.impulse_scale; + velocity_bias = MaxF32(softness.bias_rate * separation, -pushout_velocity); + } + + Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0)); + Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1)); + Vec2 vrel = SubVec2(vel0, vel1); + + f32 k = contact->inv_normal_mass; + + // To be applied along normal + f32 vn = DotVec2(vrel, normal); + f32 j = ((k * mass_scale) * (vn - velocity_bias)) - (contact->solved_normal_impulse * impulse_scale); + + f32 old_impulse = contact->solved_normal_impulse; + f32 new_impulse = MaxF32(old_impulse + j, 0); + f32 delta = new_impulse - old_impulse; + contact->solved_normal_impulse = new_impulse; + + Vec2 impulse = MulVec2(normal, delta); + v0 = SubVec2(v0, MulVec2(impulse, inv_m0)); + v1 = AddVec2(v1, MulVec2(impulse, inv_m1)); + w0 -= WedgeVec2(vcp0, impulse) * inv_i0; + w1 += WedgeVec2(vcp1, impulse) * inv_i1; + } + + // Tangent impulse + for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) + { + P_ContactPoint *contact = &constraint->points[contact_idx]; + Vec2 vcp0 = contact->vcp0; + Vec2 vcp1 = contact->vcp1; + + Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0)); + Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1)); + Vec2 vrel = SubVec2(vel0, vel1); + + f32 k = contact->inv_tangent_mass; + + // To be applied along tangent + f32 vt = DotVec2(vrel, tangent); + f32 j = vt * k; + + f32 max_friction = constraint->friction * contact->solved_normal_impulse; + f32 old_impulse = contact->solved_tangent_impulse; + f32 new_impulse = ClampF32(old_impulse + j, -max_friction, max_friction); + f32 delta = new_impulse - old_impulse; + contact->solved_tangent_impulse = new_impulse; + + Vec2 impulse = MulVec2(tangent, delta); + v0 = SubVec2(v0, MulVec2(impulse, inv_m0)); + v1 = AddVec2(v1, MulVec2(impulse, inv_m1)); + w0 -= WedgeVec2(vcp0, impulse) * inv_i0; + w1 += WedgeVec2(vcp1, impulse) * inv_i1; + } + + if (!P_IsEntNil(ent0)) + { + ent0->v = v0; + ent0->w = w0; + } + if (!P_IsEntNil(ent1)) + { + ent1->v = v1; + ent1->w = w1; + } + } + + //- Solve gentle constraint + if (constraint->flags & P_ConstraintFlag_Gentle) + { + // Normal impulse + for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) + { + P_ContactPoint *contact = &constraint->points[contact_idx]; + Vec2 vcp0 = contact->vcp0; + Vec2 vcp1 = contact->vcp1; + Vec2 p0 = AddVec2(center0, vcp0); + Vec2 p1 = AddVec2(center1, vcp1); + f32 separation = DotVec2(SubVec2(p1, p0), normal) + contact->starting_separation; + + f32 j = -separation * solver_dt * gentle_pushout_factor; + + f32 old_impulse = contact->solved_normal_impulse; + f32 new_impulse = MaxF32(old_impulse + j, 0); + f32 delta = new_impulse - old_impulse; + contact->solved_normal_impulse = new_impulse; + + Vec2 impulse = MulVec2(normal, delta); + v0 = SubVec2(v0, MulVec2(impulse, inv_m0)); + v1 = AddVec2(v1, MulVec2(impulse, inv_m1)); + w0 -= WedgeVec2(vcp0, impulse) * inv_i0; + w1 += WedgeVec2(vcp1, impulse) * inv_i1; + } + + if (!P_IsEntNil(ent0)) + { + ent0->v = v0; + ent0->w = w0; + } + if (!P_IsEntNil(ent1)) + { + ent1->v = v1; + ent1->w = w1; + } + } + } + + ////////////////////////////// + //- Integrate velocities + + for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + if (!ent->is_bullet && ent->sim) + { + Xform xf = ent->xf; + xf.t = AddVec2(xf.t, MulVec2(ent->v, solver_dt)); + xf.r = RotateVec2Angle(xf.r, ent->w * solver_dt); + ent->xf = xf; } } } ////////////////////////////// - //- Solve constraints + //- Build post-solve space from ents - // TODO: Solve wall constraints last - - for (P_Constraint *constraint = P_FirstConstraint(frame); !P_IsConstraintNil(constraint); constraint = P_NextConstraint(constraint)) - { - P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0); - P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); - - Vec2 normal = constraint->normal; - Vec2 tangent = PerpVec2(normal); - - f32 inv_m0 = constraint->inv_m0; - f32 inv_m1 = constraint->inv_m1; - f32 inv_i0 = constraint->inv_i0; - f32 inv_i1 = constraint->inv_i1; - Vec2 v0 = ent0->v; - Vec2 v1 = ent1->v; - f32 w0 = ent0->w; - f32 w1 = ent1->w; - - Vec2 center0 = constraint->static_center0; - Vec2 center1 = constraint->static_center1; - if (!P_IsEntNil(ent0)) - { - center0 = P_WorldShapeFromEnt(ent0).center_of_mass; - } - if (!P_IsEntNil(ent1)) - { - center1 = P_WorldShapeFromEnt(ent1).center_of_mass; - } - - //- Solve solid constraint - if (constraint->flags & P_ConstraintFlag_Solid) - { - // Normal impulse - for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) - { - P_ContactPoint *contact = &constraint->points[contact_idx]; - Vec2 vcp0 = contact->vcp0; - Vec2 vcp1 = contact->vcp1; - Vec2 p0 = AddVec2(center0, vcp0); - Vec2 p1 = AddVec2(center1, vcp1); - f32 separation = DotVec2(SubVec2(p1, p0), normal) + contact->starting_separation; - - f32 velocity_bias = 0.0; - f32 mass_scale = 1.0; - f32 impulse_scale = 0.0; - - // TODO: Do a relaxation pass without bias - b32 apply_bias = 1; - if (separation > 0.0) - { - // Speculative - velocity_bias = separation / solver_dt; - } - else if (apply_bias) - { - // Soft constraint - SoftSpring softness = solid_spring; - f32 pushout_velocity = solid_pushout_velocity; - mass_scale = softness.mass_scale; - impulse_scale = softness.impulse_scale; - velocity_bias = MaxF32(softness.bias_rate * separation, -pushout_velocity); - } - - Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0)); - Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1)); - Vec2 vrel = SubVec2(vel0, vel1); - - f32 k = contact->inv_normal_mass; - - // To be applied along normal - f32 vn = DotVec2(vrel, normal); - f32 j = ((k * mass_scale) * (vn - velocity_bias)) - (contact->solved_normal_impulse * impulse_scale); - - f32 old_impulse = contact->solved_normal_impulse; - f32 new_impulse = MaxF32(old_impulse + j, 0); - f32 delta = new_impulse - old_impulse; - contact->solved_normal_impulse = new_impulse; - - Vec2 impulse = MulVec2(normal, delta); - v0 = SubVec2(v0, MulVec2(impulse, inv_m0)); - v1 = AddVec2(v1, MulVec2(impulse, inv_m1)); - w0 -= WedgeVec2(vcp0, impulse) * inv_i0; - w1 += WedgeVec2(vcp1, impulse) * inv_i1; - } - - // Tangent impulse - for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) - { - P_ContactPoint *contact = &constraint->points[contact_idx]; - Vec2 vcp0 = contact->vcp0; - Vec2 vcp1 = contact->vcp1; - - Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0)); - Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1)); - Vec2 vrel = SubVec2(vel0, vel1); - - f32 k = contact->inv_tangent_mass; - - // To be applied along tangent - f32 vt = DotVec2(vrel, tangent); - f32 j = vt * k; - - f32 max_friction = constraint->friction * contact->solved_normal_impulse; - f32 old_impulse = contact->solved_tangent_impulse; - f32 new_impulse = ClampF32(old_impulse + j, -max_friction, max_friction); - f32 delta = new_impulse - old_impulse; - contact->solved_tangent_impulse = new_impulse; - - Vec2 impulse = MulVec2(tangent, delta); - v0 = SubVec2(v0, MulVec2(impulse, inv_m0)); - v1 = AddVec2(v1, MulVec2(impulse, inv_m1)); - w0 -= WedgeVec2(vcp0, impulse) * inv_i0; - w1 += WedgeVec2(vcp1, impulse) * inv_i1; - } - - if (!P_IsEntNil(ent0)) - { - ent0->v = v0; - ent0->w = w0; - } - if (!P_IsEntNil(ent1)) - { - ent1->v = v1; - ent1->w = w1; - } - } - - //- Solve gentle constraint - if (constraint->flags & P_ConstraintFlag_Gentle) - { - // Normal impulse - for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx) - { - P_ContactPoint *contact = &constraint->points[contact_idx]; - Vec2 vcp0 = contact->vcp0; - Vec2 vcp1 = contact->vcp1; - Vec2 p0 = AddVec2(center0, vcp0); - Vec2 p1 = AddVec2(center1, vcp1); - f32 separation = DotVec2(SubVec2(p1, p0), normal) + contact->starting_separation; - - f32 j = -separation * solver_dt * gentle_pushout_factor; - - f32 old_impulse = contact->solved_normal_impulse; - f32 new_impulse = MaxF32(old_impulse + j, 0); - f32 delta = new_impulse - old_impulse; - contact->solved_normal_impulse = new_impulse; - - Vec2 impulse = MulVec2(normal, delta); - v0 = SubVec2(v0, MulVec2(impulse, inv_m0)); - v1 = AddVec2(v1, MulVec2(impulse, inv_m1)); - w0 -= WedgeVec2(vcp0, impulse) * inv_i0; - w1 += WedgeVec2(vcp1, impulse) * inv_i1; - } - - if (!P_IsEntNil(ent0)) - { - ent0->v = v0; - ent0->w = w0; - } - if (!P_IsEntNil(ent1)) - { - ent1->v = v1; - ent1->w = w1; - } - } - } + P_Space post_solve_ents_space = P_SpaceFromEnts(scratch.arena, frame); ////////////////////////////// - //- Integrate velocities + //- Spawn bullets + + + + + + + + + + + + + + + + + // TODO: Remove this - for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { - if (!ent->is_bullet && ent->sim) + P_EntList bullets_to_spawn = Zi; + for (P_Ent *firer = P_FirstEnt(frame); !P_IsEntNil(firer); firer = P_NextEnt(firer)) { - Xform xf = ent->xf; - xf.t = AddVec2(xf.t, MulVec2(ent->v, solver_dt)); - xf.r = RotateVec2Angle(xf.r, ent->w * solver_dt); - ent->xf = xf; - } - } - } - - ////////////////////////////// - //- Build post-solve space from ents - - P_Space post_solve_ents_space = P_SpaceFromEnts(scratch.arena, frame); - - ////////////////////////////// - //- Spawn bullets - - - - - - - - - - - - - - - - - // TODO: Remove this - - { - P_EntList bullets_to_spawn = Zi; - for (P_Ent *firer = P_FirstEnt(frame); !P_IsEntNil(firer); firer = P_NextEnt(firer)) - { - P_Ent *weapon = P_EntFromKey(frame, firer->weapon); - if (weapon->is_weapon && (firer->control.fire_held || firer->control.fire_presses)) - // if (weapon->is_weapon && firer->control.fire_presses) - { - f32 fire_rate = 50; - f32 bullets_per_fire = 1; - - b32 can_fire = (weapon->last_fire_ns + NsFromSeconds(1.0 / fire_rate)) <= frame->time_ns; - - if (can_fire) + P_Ent *weapon = P_EntFromKey(frame, firer->weapon); + if (weapon->is_weapon && (firer->control.fire_held || firer->control.fire_presses)) + // if (weapon->is_weapon && firer->control.fire_presses) { - i64 tick_bullets_count = bullets_per_fire; - if (tick_bullets_count > 0) + f32 fire_rate = 50; + f32 bullets_per_fire = 1; + + b32 can_fire = (weapon->last_fire_ns + NsFromSeconds(1.0 / fire_rate)) <= frame->time_ns; + + if (can_fire) { - for (i64 bullet_idx = 0; bullet_idx < tick_bullets_count; ++bullet_idx) + i64 tick_bullets_count = bullets_per_fire; + if (tick_bullets_count > 0) { - P_Ent *bullet = P_PushTempEnt(scratch.arena, &bullets_to_spawn); - bullet->is_bullet = 1; - - // TDOO: More specific key with seed that only increments on player control (for less misprediction) - bullet->key = P_EntKeyFromU64(P_RandU64FromEnt(firer)); - - - - if (weapon->is_launcher) + for (i64 bullet_idx = 0; bullet_idx < tick_bullets_count; ++bullet_idx) { - bullet->is_bomb = 1; - } + P_Ent *bullet = P_PushTempEnt(scratch.arena, &bullets_to_spawn); + bullet->is_bullet = 1; - bullet->source = weapon->key; - bullet->damage_attribution = firer->source; - bullet->sim = weapon->sim; - } - } - weapon->last_fire_ns = frame->time_ns; - } - } - } - P_SpawnEntsFromList(frame, bullets_to_spawn); - } + // TDOO: More specific key with seed that only increments on player control (for less misprediction) + bullet->key = P_EntKeyFromU64(P_RandU64FromEnt(firer)); - - - ////////////////////////////// - //- Update bullets - - - - - - - - - { - P_EntList ents_to_spawn = Zi; - for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) - { - if (bullet->is_bullet) - { - P_Ent *weapon = P_EntFromKey(frame, bullet->source); - P_Ent *firer = P_EntFromKey(frame, weapon->source); - P_Ent *damager = P_EntFromKey(frame, bullet->damage_attribution); - - b32 is_first_bullet_tick = bullet->created_at_tick == frame->tick; - - ////////////////////////////// - //- Bullet properties - - f32 spread = Tau * 0.05; - // f32 spread = Tau * 0.01; - // f32 spread = 0; - - b32 should_ricochet = 0; - - f32 initial_speed = 1; - f32 speed_falloff = 0; - if (weapon->is_uzi) - { - initial_speed = TweakFloat("Bullet speed", 50, 1, 100); - } - else if (weapon->is_launcher) - { - should_ricochet = 1; - initial_speed = 50; - // initial_speed = 100; - speed_falloff = 5; - } - - ////////////////////////////// - //- Initialize - - // if (bullet->created_at_tick != frame->tick) - // { - // Vec2 start = bullet->bullet_start; - // Vec2 end = bullet->bullet_end; - // Vec2 vel = SubVec2(end, start); - // bullet->bullet_start = end; - // bullet->bullet_end = AddVec2(end, vel); - // bullet->xf.t = bullet->bullet_start; - // bullet->xf.r = NormVec2(vel); - // } - - Struct(BulletPath) - { - BulletPath *next; - Vec2 start; - Vec2 end; - }; - BulletPath *first_bullet_path = 0; - BulletPath *last_bullet_path = 0; - - if (is_first_bullet_tick) - { - Vec2 fire_pos = Zi; - Vec2 fire_dir = Zi; - Vec2 fire_base0 = Zi; - Vec2 fire_base1 = Zi; - { - Vec2 look = firer->control.look; - P_Anim anim = P_AnimFromEnt(frame, firer); - SPR_Sprite body = SPR_SpriteFromSheet(anim.sheet, anim.span, anim.frame_seq); - SPR_Sprite wep = SPR_SpriteFromSheet(anim.wep_sheet, anim.span, anim.frame_seq); - - //- Compute sprite transforms - Affine ent_to_world_af = MulAffineXform(AffineIdentity, firer->xf); - Affine body_pix_to_world_af = AffineIdentity; - Affine wep_pix_to_world_af = AffineIdentity; - { - Vec2 pix_scale = VEC2(1.0 / P_CellsPerMeter, 1.0 / P_CellsPerMeter); - - //- Compute body transform - Affine body_pix_to_ent_af = AffineIdentity; - { - body_pix_to_ent_af = ScaleAffine(body_pix_to_ent_af, pix_scale); - - SPR_Ray anchor_ray = body.rays[SPR_RayKind_Anchor]; - body_pix_to_ent_af = RotateAffine(body_pix_to_ent_af, InvertRot(anchor_ray.dir)); - body_pix_to_ent_af = TranslateAffine(body_pix_to_ent_af, NegVec2(anchor_ray.pos)); - } - - //- Compute weapon transform - Affine wep_pix_to_ent_af = AffineIdentity; - { - wep_pix_to_ent_af = ScaleAffine(wep_pix_to_ent_af, pix_scale); - - SPR_Ray body_anchor_ray = body.rays[SPR_RayKind_Anchor]; - SPR_Ray body_ap_ray = body.rays[SPR_RayKind_Ap]; - wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, InvertRot(body_anchor_ray.dir)); - wep_pix_to_ent_af = TranslateAffine(wep_pix_to_ent_af, SubVec2(body_ap_ray.pos, body_anchor_ray.pos)); - wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, NegVec2(body_ap_ray.dir)); - - SPR_Ray anchor_ray = wep.rays[SPR_RayKind_Anchor]; - wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, anchor_ray.dir); - wep_pix_to_ent_af = TranslateAffine(wep_pix_to_ent_af, NegVec2(anchor_ray.pos)); - } - - body_pix_to_world_af = MulAffine(ent_to_world_af, body_pix_to_ent_af); - wep_pix_to_world_af = MulAffine(ent_to_world_af, wep_pix_to_ent_af); - } - - SPR_Ray muzzle_ray = wep.rays[SPR_RayKind_Ap]; - fire_pos = MulAffineVec2(wep_pix_to_world_af, muzzle_ray.pos); - fire_dir = NormVec2(MulAffineBasisVec2(wep_pix_to_world_af, muzzle_ray.dir)); - - fire_base0 = MulVec2(PerpVec2(NegVec2(firer->xf.r)), WedgeVec2(firer->xf.r, SubVec2(firer->xf.t, fire_pos))); - fire_base0 = AddVec2(fire_base0, firer->xf.t); - - Vec2 chamber_pos = MulAffineVec2(body_pix_to_world_af, body.rays[SPR_RayKind_Ap].pos); - - fire_base1 = MulVec2(PerpVec2(fire_dir), WedgeVec2(fire_dir, SubVec2(fire_pos, chamber_pos))); - fire_base1 = AddVec2(fire_base1, chamber_pos); - - P_DebugDrawLine(fire_base0, fire_base1, Color_Yellow); - P_DebugDrawLine(fire_base1, fire_pos, Color_Green); - P_DebugDrawPoint(fire_base0, Color_Yellow); - P_DebugDrawPoint(fire_base1, Color_Green); - P_DebugDrawPoint(fire_pos, Color_Red); - - Vec2 bullet_dir = RotateVec2Angle(fire_dir, spread * (Norm24(MixU64s(bullet->key.v, P_BulletSpreadBasis)) - 0.5)); - - bullet->xf.t = fire_pos; - bullet->xf.r = NormVec2(bullet_dir); - - bullet->v = MulVec2(NormVec2(bullet_dir), initial_speed); - // bullet->v = AddVec2(bullet->v, firer->v); - } - - - - - - - - - // On bullet's first tick, we want to ensure that the firer/weapon - // wasn't obstructed (e.g. to prevent shooting through walls), so we - // insert a path from the bullet's base to its starting position before - // its actual firing path - { - // Firer origin -> weapon chamber path - BulletPath *path = PushStruct(scratch.arena, BulletPath); - SllQueuePush(first_bullet_path, last_bullet_path, path); - path->start = fire_base0; - path->end = fire_base1; - } - { - // Weapon chamber -> bullet start path - BulletPath *path = PushStruct(scratch.arena, BulletPath); - SllQueuePush(first_bullet_path, last_bullet_path, path); - path->start = fire_base1; - path->end = fire_pos; - } - } - - Vec2 dir = MulVec2(bullet->v, sim_dt); - Vec2 p0 = bullet->xf.t; - Vec2 p1 = p0; - - // Cur pos -> next pos path - // if (!is_first_bullet_tick) - { - p1 = AddVec2(p0, dir); - BulletPath *path = PushStruct(scratch.arena, BulletPath); - SllQueuePush(first_bullet_path, last_bullet_path, path); - path->start = p0; - path->end = p1; - } - - P_EntKey victim_key = Zi; - P_RaycastResult victim_raycast = Zi; - b32 has_hit = 0; - { - for (BulletPath *path = first_bullet_path; path && !has_hit; path = path->next) - { - Vec2 path_dir = SubVec2(path->end, path->start); - P_Space *cast_spaces[] = { - &world->walls_space, - &post_solve_ents_space, - }; - P_SpaceEntryList cast_entries = Zi; - P_UniqueSpaceEntriesFromRay( - scratch.arena, - &cast_entries, - countof(cast_spaces), - cast_spaces, - path->start, - path->end - ); - f32 closest_len_sq = Inf; - for (P_SpaceEntryNode *entry_node = cast_entries.first; entry_node; entry_node = entry_node->next) - { - P_SpaceEntry *entry = &entry_node->entry; - P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; - P_Ent *potential_victim = P_EntFromKey(frame, potential_victim_key); - b32 can_hit = ( - P_IsEntNil(potential_victim) || - (potential_victim->is_guy && (!P_MatchEntKey(potential_victim->key, firer->key) || P_IsEntKeyNil(firer->key))) - ); - if (can_hit) - { - P_Shape potential_victim_shape = entry->shape; - P_RaycastResult entrance_raycast = P_RaycastShape(potential_victim_shape, path->start, path_dir); - Vec2 entrance = entrance_raycast.p; - if (entrance_raycast.is_intersecting) + if (weapon->is_launcher) { - P_RaycastResult exit_raycast = P_RaycastShape(potential_victim_shape, path->start, NegVec2(path_dir)); - Vec2 exit = exit_raycast.p; - f32 da = DotVec2(path_dir, SubVec2(entrance, path->start)); - f32 db = DotVec2(path_dir, SubVec2(exit, path->start)); - if (db > 0 && (da <= Vec2LenSq(path_dir) || da <= 0)) + bullet->is_bomb = 1; + } + + bullet->source = weapon->key; + bullet->damage_attribution = firer->source; + bullet->sim = weapon->sim; + } + } + weapon->last_fire_ns = frame->time_ns; + } + } + } + P_SpawnEntsFromList(frame, bullets_to_spawn); + } + + + + + + ////////////////////////////// + //- Update bullets + + + + + + + + + { + P_EntList ents_to_spawn = Zi; + for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) + { + if (bullet->is_bullet) + { + P_Ent *weapon = P_EntFromKey(frame, bullet->source); + P_Ent *firer = P_EntFromKey(frame, weapon->source); + P_Ent *damager = P_EntFromKey(frame, bullet->damage_attribution); + + b32 is_first_bullet_tick = bullet->created_at_tick == frame->tick; + + ////////////////////////////// + //- Bullet properties + + f32 spread = Tau * 0.05; + // f32 spread = Tau * 0.01; + // f32 spread = 0; + + b32 should_ricochet = 0; + + f32 initial_speed = 1; + f32 speed_falloff = 0; + if (weapon->is_uzi) + { + initial_speed = TweakFloat("Bullet speed", 50, 1, 100); + } + else if (weapon->is_launcher) + { + should_ricochet = 1; + initial_speed = 50; + // initial_speed = 100; + speed_falloff = 5; + } + + ////////////////////////////// + //- Initialize + + // if (bullet->created_at_tick != frame->tick) + // { + // Vec2 start = bullet->bullet_start; + // Vec2 end = bullet->bullet_end; + // Vec2 vel = SubVec2(end, start); + // bullet->bullet_start = end; + // bullet->bullet_end = AddVec2(end, vel); + // bullet->xf.t = bullet->bullet_start; + // bullet->xf.r = NormVec2(vel); + // } + + Struct(BulletPath) + { + BulletPath *next; + Vec2 start; + Vec2 end; + }; + BulletPath *first_bullet_path = 0; + BulletPath *last_bullet_path = 0; + + if (is_first_bullet_tick) + { + Vec2 fire_pos = Zi; + Vec2 fire_dir = Zi; + Vec2 fire_base0 = Zi; + Vec2 fire_base1 = Zi; + { + Vec2 look = firer->control.look; + P_Anim anim = P_AnimFromEnt(frame, firer); + SPR_Sprite body = SPR_SpriteFromSheet(anim.sheet, anim.span, anim.frame_seq); + SPR_Sprite wep = SPR_SpriteFromSheet(anim.wep_sheet, anim.span, anim.frame_seq); + + //- Compute sprite transforms + Affine ent_to_world_af = MulAffineXform(AffineIdentity, firer->xf); + Affine body_pix_to_world_af = AffineIdentity; + Affine wep_pix_to_world_af = AffineIdentity; + { + Vec2 pix_scale = VEC2(1.0 / P_CellsPerMeter, 1.0 / P_CellsPerMeter); + + //- Compute body transform + Affine body_pix_to_ent_af = AffineIdentity; + { + body_pix_to_ent_af = ScaleAffine(body_pix_to_ent_af, pix_scale); + + SPR_Ray anchor_ray = body.rays[SPR_RayKind_Anchor]; + body_pix_to_ent_af = RotateAffine(body_pix_to_ent_af, InvertRot(anchor_ray.dir)); + body_pix_to_ent_af = TranslateAffine(body_pix_to_ent_af, NegVec2(anchor_ray.pos)); + } + + //- Compute weapon transform + Affine wep_pix_to_ent_af = AffineIdentity; + { + wep_pix_to_ent_af = ScaleAffine(wep_pix_to_ent_af, pix_scale); + + SPR_Ray body_anchor_ray = body.rays[SPR_RayKind_Anchor]; + SPR_Ray body_ap_ray = body.rays[SPR_RayKind_Ap]; + wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, InvertRot(body_anchor_ray.dir)); + wep_pix_to_ent_af = TranslateAffine(wep_pix_to_ent_af, SubVec2(body_ap_ray.pos, body_anchor_ray.pos)); + wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, NegVec2(body_ap_ray.dir)); + + SPR_Ray anchor_ray = wep.rays[SPR_RayKind_Anchor]; + wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, anchor_ray.dir); + wep_pix_to_ent_af = TranslateAffine(wep_pix_to_ent_af, NegVec2(anchor_ray.pos)); + } + + body_pix_to_world_af = MulAffine(ent_to_world_af, body_pix_to_ent_af); + wep_pix_to_world_af = MulAffine(ent_to_world_af, wep_pix_to_ent_af); + } + + SPR_Ray muzzle_ray = wep.rays[SPR_RayKind_Ap]; + fire_pos = MulAffineVec2(wep_pix_to_world_af, muzzle_ray.pos); + fire_dir = NormVec2(MulAffineBasisVec2(wep_pix_to_world_af, muzzle_ray.dir)); + + fire_base0 = MulVec2(PerpVec2(NegVec2(firer->xf.r)), WedgeVec2(firer->xf.r, SubVec2(firer->xf.t, fire_pos))); + fire_base0 = AddVec2(fire_base0, firer->xf.t); + + Vec2 chamber_pos = MulAffineVec2(body_pix_to_world_af, body.rays[SPR_RayKind_Ap].pos); + + fire_base1 = MulVec2(PerpVec2(fire_dir), WedgeVec2(fire_dir, SubVec2(fire_pos, chamber_pos))); + fire_base1 = AddVec2(fire_base1, chamber_pos); + + P_DebugDrawLine(fire_base0, fire_base1, Color_Yellow); + P_DebugDrawLine(fire_base1, fire_pos, Color_Green); + P_DebugDrawPoint(fire_base0, Color_Yellow); + P_DebugDrawPoint(fire_base1, Color_Green); + P_DebugDrawPoint(fire_pos, Color_Red); + + Vec2 bullet_dir = RotateVec2Angle(fire_dir, spread * (Norm24(MixU64s(bullet->key.v, P_BulletSpreadBasis)) - 0.5)); + + bullet->xf.t = fire_pos; + bullet->xf.r = NormVec2(bullet_dir); + + bullet->v = MulVec2(NormVec2(bullet_dir), initial_speed); + // bullet->v = AddVec2(bullet->v, firer->v); + } + + + + + + + + + // On bullet's first tick, we want to ensure that the firer/weapon + // wasn't obstructed (e.g. to prevent shooting through walls), so we + // insert a path from the bullet's base to its starting position before + // its actual firing path + { + // Firer origin -> weapon chamber path + BulletPath *path = PushStruct(scratch.arena, BulletPath); + SllQueuePush(first_bullet_path, last_bullet_path, path); + path->start = fire_base0; + path->end = fire_base1; + } + { + // Weapon chamber -> bullet start path + BulletPath *path = PushStruct(scratch.arena, BulletPath); + SllQueuePush(first_bullet_path, last_bullet_path, path); + path->start = fire_base1; + path->end = fire_pos; + } + } + + Vec2 dir = MulVec2(bullet->v, sim_dt); + Vec2 p0 = bullet->xf.t; + Vec2 p1 = p0; + + // Cur pos -> next pos path + // if (!is_first_bullet_tick) + { + p1 = AddVec2(p0, dir); + BulletPath *path = PushStruct(scratch.arena, BulletPath); + SllQueuePush(first_bullet_path, last_bullet_path, path); + path->start = p0; + path->end = p1; + } + + P_EntKey victim_key = Zi; + P_RaycastResult victim_raycast = Zi; + b32 has_hit = 0; + { + for (BulletPath *path = first_bullet_path; path && !has_hit; path = path->next) + { + Vec2 path_dir = SubVec2(path->end, path->start); + P_Space *cast_spaces[] = { + &world->walls_space, + &post_solve_ents_space, + }; + P_SpaceEntryList cast_entries = Zi; + P_UniqueSpaceEntriesFromRay( + scratch.arena, + &cast_entries, + countof(cast_spaces), + cast_spaces, + path->start, + path->end + ); + f32 closest_len_sq = Inf; + for (P_SpaceEntryNode *entry_node = cast_entries.first; entry_node; entry_node = entry_node->next) + { + P_SpaceEntry *entry = &entry_node->entry; + P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; + P_Ent *potential_victim = P_EntFromKey(frame, potential_victim_key); + b32 can_hit = ( + P_IsEntNil(potential_victim) || + (potential_victim->is_guy && (!P_MatchEntKey(potential_victim->key, firer->key) || P_IsEntKeyNil(firer->key))) + ); + if (can_hit) + { + P_Shape potential_victim_shape = entry->shape; + P_RaycastResult entrance_raycast = P_RaycastShape(potential_victim_shape, path->start, path_dir); + Vec2 entrance = entrance_raycast.p; + if (entrance_raycast.is_intersecting) { - f32 len_sq = Vec2LenSq(SubVec2(entrance_raycast.p, path->start)); - if (len_sq < closest_len_sq) + P_RaycastResult exit_raycast = P_RaycastShape(potential_victim_shape, path->start, NegVec2(path_dir)); + Vec2 exit = exit_raycast.p; + f32 da = DotVec2(path_dir, SubVec2(entrance, path->start)); + f32 db = DotVec2(path_dir, SubVec2(exit, path->start)); + if (db > 0 && (da <= Vec2LenSq(path_dir) || da <= 0)) { - closest_len_sq = len_sq; - victim_key = potential_victim_key; - victim_raycast = entrance_raycast; - has_hit = 1; + f32 len_sq = Vec2LenSq(SubVec2(entrance_raycast.p, path->start)); + if (len_sq < closest_len_sq) + { + closest_len_sq = len_sq; + victim_key = potential_victim_key; + victim_raycast = entrance_raycast; + has_hit = 1; + } } } } } } } - } - P_Ent *victim = P_EntFromKey(frame, victim_key); + P_Ent *victim = P_EntFromKey(frame, victim_key); - // Create hit - Vec2 final_pos = p1; - if (has_hit) - { - Vec2 normal = victim_raycast.normal; - bullet->bullet_hits_count += 1; - - P_Ent *hit = P_PushTempEnt(scratch.arena, &ents_to_spawn); + // Create hit + Vec2 final_pos = p1; + if (has_hit) { - hit->key = P_EntKeyFromU64(MixU64s(bullet->key.v, P_BulletHitBasis + bullet->bullet_hits_count)); - hit->is_hit = 1; - hit->hit_entry = victim_raycast.p; - hit->hit_entry_normal = normal; - hit->lifetime_seconds = P_ObservationDurationSeconds; - // FIXME: Use relative velocity at collision point - hit->hit_entry_velocity = bullet->v; - if (victim->is_guy) + Vec2 normal = victim_raycast.normal; + bullet->bullet_hits_count += 1; + + P_Ent *hit = P_PushTempEnt(scratch.arena, &ents_to_spawn); { - hit->hit_material = P_MaterialKind_Flesh; + hit->key = P_EntKeyFromU64(MixU64s(bullet->key.v, P_BulletHitBasis + bullet->bullet_hits_count)); + hit->is_hit = 1; + hit->hit_entry = victim_raycast.p; + hit->hit_entry_normal = normal; + hit->lifetime_seconds = P_ObservationDurationSeconds; + // FIXME: Use relative velocity at collision point + hit->hit_entry_velocity = bullet->v; + if (victim->is_guy) + { + hit->hit_material = P_MaterialKind_Flesh; + } + else + { + hit->hit_material = P_MaterialKind_Wall; + } + } + + // Reflect velocity along normal + if (should_ricochet) + { + bullet->v = SubVec2(bullet->v, MulVec2(normal, 2 * DotVec2(bullet->v, normal))); + bullet->v = MulVec2(bullet->v, 0.5); } else { - hit->hit_material = P_MaterialKind_Wall; + bullet->exists = 0; } + + f32 collision_offset = 0.01; // Tiny offset along normal to prevent collision with the victim during ricochets + final_pos = AddVec2(hit->hit_entry, MulVec2(hit->hit_entry_normal, collision_offset)); } - // Reflect velocity along normal - if (should_ricochet) + bullet->xf.t = final_pos; + if (Vec2LenSq(dir) > (0.001 * 0.001)) { - bullet->v = SubVec2(bullet->v, MulVec2(normal, 2 * DotVec2(bullet->v, normal))); - bullet->v = MulVec2(bullet->v, 0.5); + bullet->xf.r = NormVec2(dir); } - else + bullet->v = MulVec2(bullet->v, 1.0 - SaturateF32(speed_falloff * sim_dt)); + + // Create trail + { + P_Ent *trail = P_PushTempEnt(scratch.arena, &ents_to_spawn); + trail->key = P_EntKeyFromU64(MixU64s(bullet->key.v, P_BulletTrailBasis + frame->tick)); + trail->is_trail = 1; + trail->trail_p0 = p0; + trail->trail_p1 = bullet->xf.t; + trail->lifetime_seconds = 0; + + } + + // TODO: Remove this + if (!is_client && !P_IsEntNil(victim)) + { + if (damager->is_player) + { + victim->damage_attribution = damager->key; + } + // victim->health -= 0.25; + } + + // Prune out of bounds bullet + Rng2 bounds = Zi; + bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2); + bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2); + if ( + bullet->xf.t.x < bounds.p0.x || bullet->xf.t.y < bounds.p0.y || + bullet->xf.t.x > bounds.p1.x || bullet->xf.t.y > bounds.p1.y + ) { bullet->exists = 0; } - - f32 collision_offset = 0.01; // Tiny offset along normal to prevent collision with the victim during ricochets - final_pos = AddVec2(hit->hit_entry, MulVec2(hit->hit_entry_normal, collision_offset)); - } - - bullet->xf.t = final_pos; - if (Vec2LenSq(dir) > (0.001 * 0.001)) - { - bullet->xf.r = NormVec2(dir); - } - bullet->v = MulVec2(bullet->v, 1.0 - SaturateF32(speed_falloff * sim_dt)); - - // Create trail - { - P_Ent *trail = P_PushTempEnt(scratch.arena, &ents_to_spawn); - trail->key = P_EntKeyFromU64(MixU64s(bullet->key.v, P_BulletTrailBasis + frame->tick)); - trail->is_trail = 1; - trail->trail_p0 = p0; - trail->trail_p1 = bullet->xf.t; - trail->lifetime_seconds = 0; - - } - - // TODO: Remove this - if (!is_client && !P_IsEntNil(victim)) - { - if (damager->is_player) - { - victim->damage_attribution = damager->key; - } - // victim->health -= 0.25; - } - - // Prune out of bounds bullet - Rng2 bounds = Zi; - bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2); - bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2); - if ( - bullet->xf.t.x < bounds.p0.x || bullet->xf.t.y < bounds.p0.y || - bullet->xf.t.x > bounds.p1.x || bullet->xf.t.y > bounds.p1.y - ) - { - bullet->exists = 0; } } + P_SpawnEntsFromList(frame, ents_to_spawn); } - P_SpawnEntsFromList(frame, ents_to_spawn); - } @@ -3519,212 +3522,212 @@ void P_StepFrame(P_Frame *frame) - // // TODO: Separate 'hits' from bullets, so that bullets can have multiple hits + // // TODO: Separate 'hits' from bullets, so that bullets can have multiple hits - // for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) - // { - // if (bullet->is_bullet) - // { - // P_Ent *weapon = P_EntFromKey(frame, bullet->source); - // P_Ent *firer = P_EntFromKey(frame, weapon->source); - // P_Ent *damager = P_EntFromKey(frame, bullet->damage_attribution); + // for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) + // { + // if (bullet->is_bullet) + // { + // P_Ent *weapon = P_EntFromKey(frame, bullet->source); + // P_Ent *firer = P_EntFromKey(frame, weapon->source); + // P_Ent *damager = P_EntFromKey(frame, bullet->damage_attribution); - // if (bullet->created_at_tick != frame->tick) - // { - // Vec2 start = bullet->bullet_start; - // Vec2 end = bullet->bullet_end; - // Vec2 vel = SubVec2(end, start); - // bullet->bullet_start = end; - // bullet->bullet_end = AddVec2(end, vel); - // bullet->xf.t = bullet->bullet_start; - // bullet->xf.r = NormVec2(vel); - // } + // if (bullet->created_at_tick != frame->tick) + // { + // Vec2 start = bullet->bullet_start; + // Vec2 end = bullet->bullet_end; + // Vec2 vel = SubVec2(end, start); + // bullet->bullet_start = end; + // bullet->bullet_end = AddVec2(end, vel); + // bullet->xf.t = bullet->bullet_start; + // bullet->xf.r = NormVec2(vel); + // } - // bullet->has_hit = 0; + // bullet->has_hit = 0; - // Struct(BulletPath) - // { - // BulletPath *next; - // Vec2 start; - // Vec2 end; - // }; - // BulletPath *first_bullet_path = 0; - // BulletPath *last_bullet_path = 0; + // Struct(BulletPath) + // { + // BulletPath *next; + // Vec2 start; + // Vec2 end; + // }; + // BulletPath *first_bullet_path = 0; + // BulletPath *last_bullet_path = 0; - // if (bullet->created_at_tick == frame->tick) - // { - // // On bullet's first tick, we want to ensure that the firer/weapon - // // wasn't obstructed (e.g. to prevent shooting through walls), so we - // // insert a path from the bullet's base to its starting position before - // // its actual firing path - // { - // // Firer origin -> weapon chamber path - // BulletPath *path = PushStruct(scratch.arena, BulletPath); - // SllQueuePush(first_bullet_path, last_bullet_path, path); - // path->start = bullet->bullet_base0; - // path->end = bullet->bullet_base1; - // } - // { - // // Weapon chamber -> bullet start path - // BulletPath *path = PushStruct(scratch.arena, BulletPath); - // SllQueuePush(first_bullet_path, last_bullet_path, path); - // path->start = bullet->bullet_base1; - // path->end = bullet->bullet_start; - // } - // } + // if (bullet->created_at_tick == frame->tick) + // { + // // On bullet's first tick, we want to ensure that the firer/weapon + // // wasn't obstructed (e.g. to prevent shooting through walls), so we + // // insert a path from the bullet's base to its starting position before + // // its actual firing path + // { + // // Firer origin -> weapon chamber path + // BulletPath *path = PushStruct(scratch.arena, BulletPath); + // SllQueuePush(first_bullet_path, last_bullet_path, path); + // path->start = bullet->bullet_base0; + // path->end = bullet->bullet_base1; + // } + // { + // // Weapon chamber -> bullet start path + // BulletPath *path = PushStruct(scratch.arena, BulletPath); + // SllQueuePush(first_bullet_path, last_bullet_path, path); + // path->start = bullet->bullet_base1; + // path->end = bullet->bullet_start; + // } + // } - // { - // BulletPath *path = PushStruct(scratch.arena, BulletPath); - // SllQueuePush(first_bullet_path, last_bullet_path, path); - // path->start = bullet->bullet_start; - // path->end = bullet->bullet_end; - // } + // { + // BulletPath *path = PushStruct(scratch.arena, BulletPath); + // SllQueuePush(first_bullet_path, last_bullet_path, path); + // path->start = bullet->bullet_start; + // path->end = bullet->bullet_end; + // } - // P_EntKey victim_key = Zi; - // P_RaycastResult victim_raycast = Zi; - // b32 hit = 0; - // { - // for (BulletPath *path = first_bullet_path; path && !hit; path = path->next) - // { - // Vec2 path_dir = SubVec2(path->end, path->start); - // P_Space *cast_spaces[] = { - // &world->walls_space, - // &post_solve_ents_space, - // }; - // P_SpaceEntryList cast_entries = Zi; - // P_UniqueSpaceEntriesFromRay( - // scratch.arena, - // &cast_entries, - // countof(cast_spaces), - // cast_spaces, - // path->start, - // path->end - // ); - // f32 closest_len_sq = Inf; - // for (P_SpaceEntryNode *entry_node = cast_entries.first; entry_node; entry_node = entry_node->next) - // { - // P_SpaceEntry *entry = &entry_node->entry; - // P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; - // if (!P_MatchEntKey(potential_victim_key, firer->key) || P_IsEntKeyNil(firer->key)) - // { - // P_Shape potential_victim_shape = entry->shape; - // P_RaycastResult entrance_raycast = P_RaycastShape(potential_victim_shape, path->start, path_dir); - // Vec2 entrance = entrance_raycast.p; - // if (entrance_raycast.is_intersecting) - // { - // P_RaycastResult exit_raycast = P_RaycastShape(potential_victim_shape, path->start, NegVec2(path_dir)); - // Vec2 exit = exit_raycast.p; - // f32 da = DotVec2(path_dir, SubVec2(entrance, path->start)); - // f32 db = DotVec2(path_dir, SubVec2(exit, path->start)); - // if (db > 0 && (da <= Vec2LenSq(path_dir) || da <= 0)) - // { - // f32 len_sq = Vec2LenSq(SubVec2(entrance_raycast.p, path->start)); - // if (len_sq < closest_len_sq) - // { - // closest_len_sq = len_sq; - // victim_key = potential_victim_key; - // victim_raycast = entrance_raycast; - // hit = 1; - // } - // } - // } - // } - // } - // } - // } - // P_Ent *victim = P_EntFromKey(frame, victim_key); + // P_EntKey victim_key = Zi; + // P_RaycastResult victim_raycast = Zi; + // b32 hit = 0; + // { + // for (BulletPath *path = first_bullet_path; path && !hit; path = path->next) + // { + // Vec2 path_dir = SubVec2(path->end, path->start); + // P_Space *cast_spaces[] = { + // &world->walls_space, + // &post_solve_ents_space, + // }; + // P_SpaceEntryList cast_entries = Zi; + // P_UniqueSpaceEntriesFromRay( + // scratch.arena, + // &cast_entries, + // countof(cast_spaces), + // cast_spaces, + // path->start, + // path->end + // ); + // f32 closest_len_sq = Inf; + // for (P_SpaceEntryNode *entry_node = cast_entries.first; entry_node; entry_node = entry_node->next) + // { + // P_SpaceEntry *entry = &entry_node->entry; + // P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; + // if (!P_MatchEntKey(potential_victim_key, firer->key) || P_IsEntKeyNil(firer->key)) + // { + // P_Shape potential_victim_shape = entry->shape; + // P_RaycastResult entrance_raycast = P_RaycastShape(potential_victim_shape, path->start, path_dir); + // Vec2 entrance = entrance_raycast.p; + // if (entrance_raycast.is_intersecting) + // { + // P_RaycastResult exit_raycast = P_RaycastShape(potential_victim_shape, path->start, NegVec2(path_dir)); + // Vec2 exit = exit_raycast.p; + // f32 da = DotVec2(path_dir, SubVec2(entrance, path->start)); + // f32 db = DotVec2(path_dir, SubVec2(exit, path->start)); + // if (db > 0 && (da <= Vec2LenSq(path_dir) || da <= 0)) + // { + // f32 len_sq = Vec2LenSq(SubVec2(entrance_raycast.p, path->start)); + // if (len_sq < closest_len_sq) + // { + // closest_len_sq = len_sq; + // victim_key = potential_victim_key; + // victim_raycast = entrance_raycast; + // hit = 1; + // } + // } + // } + // } + // } + // } + // } + // P_Ent *victim = P_EntFromKey(frame, victim_key); - // // TODO: Truncate bullet trail - // if (hit) - // { - // bullet->has_hit = 1; - // bullet->hit_entry = victim_raycast.p; - // bullet->hit_entry_normal = victim_raycast.normal; - // // bullet->bullet_end = bullet->hit_entry; - // bullet->exists = 0; - // if (victim->is_guy) - // { - // bullet->hit_material = P_MaterialKind_Flesh; - // } - // else - // { - // bullet->hit_material = P_MaterialKind_Wall; - // } - // } + // // TODO: Truncate bullet trail + // if (hit) + // { + // bullet->has_hit = 1; + // bullet->hit_entry = victim_raycast.p; + // bullet->hit_entry_normal = victim_raycast.normal; + // // bullet->bullet_end = bullet->hit_entry; + // bullet->exists = 0; + // if (victim->is_guy) + // { + // bullet->hit_material = P_MaterialKind_Flesh; + // } + // else + // { + // bullet->hit_material = P_MaterialKind_Wall; + // } + // } - // // TODO: Remove this - // if (!P_IsEntNil(victim)) - // { - // if (damager->is_player) - // { - // victim->damage_attribution = damager->key; - // } - // victim->health -= 0.25; - // } + // // TODO: Remove this + // if (!P_IsEntNil(victim)) + // { + // if (damager->is_player) + // { + // victim->damage_attribution = damager->key; + // } + // victim->health -= 0.25; + // } - // // Prune out of bounds bullet - // Rng2 bounds = Zi; - // bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2); - // bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2); - // if ( - // bullet->bullet_start.x < bounds.p0.x || bullet->bullet_start.y < bounds.p0.y || - // bullet->bullet_start.x > bounds.p1.x || bullet->bullet_start.y > bounds.p1.y - // ) - // { - // bullet->exists = 0; - // } - // } - // } + // // Prune out of bounds bullet + // Rng2 bounds = Zi; + // bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2); + // bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2); + // if ( + // bullet->bullet_start.x < bounds.p0.x || bullet->bullet_start.y < bounds.p0.y || + // bullet->bullet_start.x > bounds.p1.x || bullet->bullet_start.y > bounds.p1.y + // ) + // { + // bullet->exists = 0; + // } + // } + // } - ////////////////////////////// - //- Kill guys + ////////////////////////////// + //- Kill guys - for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) - { - if (guy->is_guy) + for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) { - if (guy->health <= 0) + if (guy->is_guy) { - P_Ent *old_guy = P_EntFromKey(prev_frame, guy->key); - if (old_guy->health > 0) + if (guy->health <= 0) { - P_Ent *player = P_EntFromKey(frame, guy->source); - P_Ent *killer = P_EntFromKey(frame, guy->damage_attribution); - if (player->is_player) + P_Ent *old_guy = P_EntFromKey(prev_frame, guy->key); + if (old_guy->health > 0) { - player->deaths += 1; + P_Ent *player = P_EntFromKey(frame, guy->source); + P_Ent *killer = P_EntFromKey(frame, guy->damage_attribution); + if (player->is_player) + { + player->deaths += 1; + } + if (killer->is_player && !P_MatchEntKey(player->key, killer->key)) + { + killer->kills += 1; + } + guy->exists = 0; + guy->continuity_gen += 1; } - if (killer->is_player && !P_MatchEntKey(player->key, killer->key)) - { - killer->kills += 1; - } - guy->exists = 0; - guy->continuity_gen += 1; } } } + + ////////////////////////////// + //- Debug draw + + P_DebugDrawFrame(frame); + + ////////////////////////////// + //- End frame + + frame->time_ns += sim_dt_ns; } - - ////////////////////////////// - //- Debug draw - - P_DebugDrawFrame(frame); - - ////////////////////////////// - //- End frame - - frame->time_ns += sim_dt_ns; - EndScratch(scratch); } diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index 4a3290c8..4e871fdb 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -109,6 +109,7 @@ void S_TickForever(WaveLaneCtx *lane) ////////////////////////////// //- Swap + ProfZoneDF("Swap sim state") { b32 swapin = IsSwappedIn(); b32 swapout = shutdown && IsSwappingOut(); @@ -166,6 +167,7 @@ void S_TickForever(WaveLaneCtx *lane) NET_Listen(net_pipe, port); P_MsgList in_msgs = Zi; + ProfZoneDF("Pop net messages") { NET_MsgList net_msgs = NET_Swap(frame_arena, net_pipe); for (NET_Msg *net_msg = net_msgs.first; net_msg; net_msg = net_msg->next) diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 8e3435dd..b6d5df20 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -3712,7 +3712,7 @@ void V_TickForever(WaveLaneCtx *lane) - b32 show_panels = frame->is_editing && !hide_editor_ui; + b32 show_panels = (frame->is_editing && !hide_editor_ui) || TweakBool("Show panels when not editing", 1); @@ -3734,475 +3734,576 @@ void V_TickForever(WaveLaneCtx *lane) - if (show_panels && V.root_panel) { - Struct(DfsPanel) { DfsPanel *next; V_Panel *panel; }; - DfsPanel *top_dfs_panel = PushStruct(frame->arena, DfsPanel); - top_dfs_panel->panel = V.root_panel; - - for (DfsPanel *dfs_panel = top_dfs_panel; dfs_panel; dfs_panel = top_dfs_panel) + Struct(BfsPanel) { BfsPanel *next; V_Panel *panel; }; + BfsPanel *first_bfs_panel = PushStruct(frame->arena, BfsPanel); + BfsPanel *last_bfs_panel = first_bfs_panel; + first_bfs_panel->panel = V.root_panel; + for (BfsPanel *parent_panel_bfs = first_bfs_panel; parent_panel_bfs; parent_panel_bfs = first_bfs_panel) { - SllStackPop(top_dfs_panel); - V_Panel *panel = dfs_panel->panel; + SllQueuePop(first_bfs_panel, last_bfs_panel); + V_Panel *parent_panel = parent_panel_bfs->panel; - for (V_Panel *child = panel->first; child; child = child->next) + for (V_Panel *panel = parent_panel->first; panel; panel = panel->next) { - DfsPanel *child_dfs = PushStruct(frame->arena, DfsPanel); - child_dfs->panel = child; - SllStackPush(top_dfs_panel, child_dfs); - } + BfsPanel *panel_bfs = PushStruct(frame->arena, BfsPanel); + panel_bfs->panel = panel; + SllQueuePush(first_bfs_panel, last_bfs_panel, panel_bfs); - - - ////////////////////////////// - //- Build profiler - - - if (panel->flags & V_PanelFlag_Profiler) - { - // FIXME: Finalize - - - - // // FIXME: Remove this - // if (frame->tick == 1) - // { - // frame->profiler.is_showing = 1; - // } - - - - - - - - - - - V_Profiler *profiler = &panel->profiler; - - f64 min_profiler_ns_per_px = 1; - // f64 max_profiler_ns_per_px = NsFromSeconds(100); - f64 max_profiler_ns_per_px = frame->time_ns / 10; - profiler->target_ns_per_px = ClampF64(profiler->target_ns_per_px, min_profiler_ns_per_px, max_profiler_ns_per_px); - profiler->ns_per_px = ClampF64(profiler->ns_per_px, min_profiler_ns_per_px, max_profiler_ns_per_px); - - UI_PushDF(OmitFlags, UI_BoxFlag_CaptureMouse * !!(frame->is_looking)) - // if (profiler->is_showing) + UI_PushDF(Tag, panel->contents_box.v) + UI_PushDF(Parent, panel->contents_box) { - // FIXME: Remove this - - // if (frame->tick == 1) - // { - // // profiler->ns_per_px = NsFromSeconds(0.0001); - // profiler->target_ns_per_px = NsFromSeconds(0.01); - // } + ////////////////////////////// + //- Build console panel - // FIXME: Remove this + if (panel->flags & V_PanelFlag_Console) + ProfZoneDF("Build console panel") { - V_ProfilerFrame *pf = &V.profiler_frames[V.profiler_frame_seq % V_ProfilerFramesCap]; - V.profiler_frame_seq += 1; + + //- Gather visible logs + Struct(VisLog) + { + VisLog *next; + LogEvent log_ev; + }; + u64 display_logs_count = 0; + VisLog *first_vis_log = 0; + VisLog *last_vis_log = 0; + + i32 max_log_level = LogLevel_Trace; + + u32 max_display_logs = 30; + i64 max_time_ns = NsFromSeconds(1000); + LogEventsArray log_events = GetLogEvents(); + { + b32 done = 0; + for (u64 log_event_idx = log_events.count; log_event_idx-- > 0 && display_logs_count < max_display_logs && !done;) + { + LogEvent ev = log_events.events[log_event_idx]; + if (ev.time_ns > (frame->time_ns - max_time_ns)) + { + if (ev.level <= max_log_level) + { + VisLog *vis_log = PushStruct(frame->arena, VisLog); + vis_log->log_ev = ev; + SllQueuePush(first_vis_log, last_vis_log, vis_log); + ++display_logs_count; + } + } + else + { + done = 1; + } + } + } + + //- Build log entries UI + UI_PushDF(ChildAlignment, UI_Region_BottomLeft) + // UI_PushDF(Anchor, UI_Region_BottomLeft) + // UI_SetNext(Height, UI_Shrink(0, 1)); + UI_PushDF(Parent, UI_BuildColumn()) + { + // UI_SetNext(BackgroundColor, VEC4(0.1, 0.5, 0.5, 0.8)); + // UI_SetNext(Height, UI_Grow(1, 0)); + // UI_BuildBox(); + + // UI_SetNext(Flags, UI_BoxFlag_Floating) + UI_SetNext(Height, UI_Shrink(0, 1)); + UI_PushDF(Parent, UI_BuildColumn()) + { + for (VisLog *vis_log = first_vis_log; vis_log; vis_log = vis_log->next) + { + LogEvent ev = vis_log->log_ev; + String log_msg = ev.msg; + String display_text = StringF(frame->arena, "%F", FmtString(log_msg)); + + // UI_BuildLabelF("%F", FmtString(text)); + UI_SetNext(Height, UI_Shrink(0, 1)); + UI_SetNext(Text, display_text); + // UI_SetNext(ChildAlignment, UI_Region_BottomLeft); + UI_SetNext(ChildAlignment, UI_Region_TopLeft); + UI_SetNext(Flags, UI_BoxFlag_DrawText); + UI_BuildBox(); + } + } + } } - //- Build profiler - UI_Key profiler_box = UI_KeyF("profiler"); - ProfZoneDF("Build profiler") - UI_PushDF(Tag, profiler_box.v) - UI_PushDF(Parent, profiler_box) + + + + + + + + + + + + + + + + + + + + + + ////////////////////////////// + //- Build profiler panel + + if (!TweakBool("Hide profiler", 0)) + if (panel->flags & V_PanelFlag_Profiler) + ProfZoneDF("Build profiler panel") { - UI_Key main_box = UI_KeyF("main"); - - // UI_Size profiler_height = UI_Fnt(70, 1); - UI_Size profiler_height = UI_Grow(1, 0); - UI_Size header_height = UI_Fnt(2, 1); - UI_Size footer_height = UI_Fnt(2, 1); - UI_Size window_padding = UI_Fnt(0.60, 1); - UI_Size minor_padding = UI_Fnt(0.15, 1); - // UI_Size graph_height = UI_Grow(0.25, 0); - UI_Size graph_height = UI_Fnt(2, 1); - UI_Size main_height = UI_Grow(1, 0); - UI_Size zone_height = UI_Fnt(1.25, 0); - UI_Size track_height = UI_Fnt(10, 0); - f32 zone_text_padding_px = UI_Top(FontSize) * 0.2; - - // Vec2 old_main_dims = DimsFromRng2(UI_Rect(main_box)); - // old_main_dims.x = MaxF32(old_main_dims.x, 1); - // old_main_dims.y = MaxF32(old_main_dims.y, 1); + // FIXME: Finalize - // if (profiler->view_start_ns == 0 && profiler->view_end_ns == 0) + // // FIXME: Remove this + // if (frame->tick == 1) // { - // profiler->view_start_ns = NsFromSeconds(0); - // profiler->view_end_ns = NsFromSeconds(10); - // } - - - // f32 old_cursor_offset_px = frame->screen_cursor.x - UI_Rect(main_box).p0.x; - // i64 old_cursor_offset_ns = (old_cursor_offset_px / old_main_dims.x) * (profiler->view_end_ns - profiler->view_start_ns); - // i64 old_cursor_ns = profiler->view_start_ns + old_cursor_offset_ns; - - f32 cursor_px = frame->screen_cursor.x - UI_Rect(main_box).p0.x; - - b32 is_main_hot = UI_HotAbsolute(main_box); - // if (is_main_hot) - { - profiler->cursor_ns = (cursor_px * profiler->ns_per_px) + profiler->view_ns; - } - - // Snap view - if (!profiler->unsnap) - { - f64 snap_width_ns = NsFromSeconds(1.0 / 10.0); - profiler->target_view_ns = frame->time_ns - snap_width_ns; - profiler->target_ns_per_px = snap_width_ns / MaxF32(DimsFromRng2(UI_Rect(main_box)).x, 100); - } - - f32 prof_zoom_rate = 1.65; - f64 prof_lerp_rate = SaturateF64(frame->dt * 25); - - { - f64 zooms = UI_Presses(main_box, Button_WheelDown) - UI_Presses(main_box, Button_WheelUp); - if (zooms != 0) - { - profiler->unsnap = 1; - } - f64 zoom_factor = PowF64(prof_zoom_rate, zooms); - profiler->target_ns_per_px *= zoom_factor; - profiler->target_view_ns = profiler->cursor_ns - (profiler->cursor_ns - profiler->target_view_ns) * zoom_factor; - if (zooms != 0) - { - profiler->last_zoom_delta = profiler->target_ns_per_px - profiler->ns_per_px; - profiler->last_view_delta = profiler->target_view_ns - profiler->view_ns; - } - f64 overshoot_factor = 0.01; - profiler->ns_per_px = LerpF64(profiler->ns_per_px, profiler->target_ns_per_px + profiler->last_zoom_delta * overshoot_factor, prof_lerp_rate); - profiler->view_ns = LerpF64(profiler->view_ns, profiler->target_view_ns + profiler->last_view_delta * overshoot_factor, prof_lerp_rate); - - if (profiler->last_zoom_delta < 0) - { - profiler->ns_per_px = MaxF64(profiler->ns_per_px, profiler->target_ns_per_px); - } - else if (profiler->last_zoom_delta > 0) - { - profiler->ns_per_px = MinF64(profiler->ns_per_px, profiler->target_ns_per_px); - } - - if (profiler->last_view_delta < 0) - { - profiler->view_ns = MaxF64(profiler->view_ns, profiler->target_view_ns); - } - else if (profiler->last_view_delta > 0) - { - profiler->view_ns = MinF64(profiler->view_ns, profiler->target_view_ns); - } - } - - - - - - - - - - - - - - - - - - - // if (profiler->is_dragging && !prev_frame->profiler.is_dragging) - // { - // profiler->drag_view_ns = profiler->view_ns; - // profiler->drag_cursor_px = cursor_px; - // } - - // if (profiler->is_dragging) - // { - // profiler->view_ns = profiler->drag_view_ns - ((cursor_px - profiler->drag_cursor_px) * profiler->ns_per_px); + // frame->profiler.is_showing = 1; // } - // FIXME: Recalc cursor after zoom - // if (is_main_hot) + + + V_Profiler *profiler = &panel->profiler; + + f64 min_profiler_ns_per_px = 1; + // f64 max_profiler_ns_per_px = NsFromSeconds(100); + f64 max_profiler_ns_per_px = frame->time_ns / 10; + profiler->target_ns_per_px = ClampF64(profiler->target_ns_per_px, min_profiler_ns_per_px, max_profiler_ns_per_px); + profiler->ns_per_px = ClampF64(profiler->ns_per_px, min_profiler_ns_per_px, max_profiler_ns_per_px); + + UI_PushDF(OmitFlags, UI_BoxFlag_CaptureMouse * !!(frame->is_looking)) + // if (profiler->is_showing) { - profiler->cursor_ns = (cursor_px * profiler->ns_per_px) + profiler->view_ns; - } + // FIXME: Remove this + + // if (frame->tick == 1) + // { + // // profiler->ns_per_px = NsFromSeconds(0.0001); + // profiler->target_ns_per_px = NsFromSeconds(0.01); + // } - - - - - // TODO: Drag in px units for sharper resolution - if (UI_Downs(main_box, Button_M2) || UI_Downs(main_box, Button_M3)) - { - profiler->drag_view_ns = profiler->view_ns; - profiler->drag_cursor_px = cursor_px; - } - - // Drag ruler - // TODO: When measuring stopped, lerp ruler closed - b32 is_measuring = 0; - if (UI_Downs(main_box, Button_M1)) - { - profiler->unsnap = 1; - profiler->ruler_start_ns = profiler->cursor_ns; - } - if (UI_Held(main_box, Button_M1)) - { - profiler->unsnap = 1; - is_measuring = 1; - profiler->target_ruler_opacity = 1; - } - else - { - profiler->target_ruler_opacity = 0; - } - profiler->ruler_opacity = LerpF32(profiler->ruler_opacity, profiler->target_ruler_opacity, prof_lerp_rate); - - // Drag view - if (UI_Held(main_box, Button_M2) || UI_Held(main_box, Button_M3)) - { - profiler->unsnap = 1; - profiler->view_ns = profiler->drag_view_ns - ((cursor_px - profiler->drag_cursor_px) * profiler->ns_per_px); - profiler->target_view_ns = profiler->view_ns; - } - - - - - - - - - // FIXME: Remove this - b32 do_break = frame->real_held_buttons[Button_B] && !prev_frame->real_held_buttons[Button_B]; - - - - - - - - // FIXME: Remove this - f64 view_range_len_px = DimsFromRng2(UI_Rect(main_box)).x; - f64 view_range_len_ns = view_range_len_px * profiler->ns_per_px; - - f64 view_start_ns = profiler->view_ns; - f64 view_end_ns = view_start_ns + view_range_len_ns; - - f64 view_start_px = view_start_ns / profiler->ns_per_px; - f64 view_end_px = view_end_ns / profiler->ns_per_px; - - - - - - - - - - - - - - - - - //- Generate visible zones - - Struct(VisZone) - { - VisZone *parent; - VisZone *next; - VisZone *next_bfs; - V_Zone *zone; - - u32 depth; - u64 id; - String name; - Vec4 color; - - f64 start_px; - f64 end_px; - - i64 start_ns; - i64 end_ns; - - u64 collapsed_count; - }; - - Struct(VisTrack) - { - VisTrack *next; - u64 id; - - u32 rows_count; - VisZone *first_zone; - VisZone *last_zone; - }; - - VisTrack *first_vis_track = 0; - VisTrack *last_vis_track = 0; - f32 zone_collapse_threshold_px = 10; - f32 zone_name_hide_threshold_px = UI_Top(FontSize) * 3; - f32 min_zone_width_px = 6; - ProfZoneDF("Generate visible zones") - { - for (V_ZoneTrack *zone_track = V.first_zone_track; zone_track; zone_track = zone_track->next) + // FIXME: Remove this { - // TODO: Ignore non-visible tracks - // TODO: Ignore non-visible depths + V_ProfilerFrame *pf = &V.profiler_frames[V.profiler_frame_seq % V_ProfilerFramesCap]; + V.profiler_frame_seq += 1; + } - VisTrack *vis_track = PushStruct(frame->arena, VisTrack); - SllQueuePush(first_vis_track, last_vis_track, vis_track); - vis_track->id = zone_track->id; - VisZone *first_bfs_vis_zone = 0; - VisZone *last_bfs_vis_zone = 0; - // Push root vis zone node to bfs + + + //- Build profiler + UI_Key profiler_box = UI_KeyF("profiler"); + UI_PushDF(Tag, profiler_box.v) + UI_PushDF(Parent, profiler_box) + { + UI_Key main_box = UI_KeyF("main"); + + // UI_Size profiler_height = UI_Fnt(70, 1); + UI_Size profiler_height = UI_Grow(1, 0); + UI_Size header_height = UI_Fnt(2, 1); + UI_Size footer_height = UI_Fnt(2, 1); + UI_Size window_padding = UI_Fnt(0.60, 1); + UI_Size minor_padding = UI_Fnt(0.15, 1); + // UI_Size graph_height = UI_Grow(0.25, 0); + UI_Size graph_height = UI_Fnt(2, 1); + UI_Size main_height = UI_Grow(1, 0); + UI_Size zone_height = UI_Fnt(1.25, 0); + // UI_Size track_height = UI_Fnt(10, 0); + UI_Size track_height = UI_Grow(1, 0); + // UI_Size track_height = UI_Shrink(0, 0); + f32 zone_text_padding_px = UI_Top(FontSize) * 0.2; + + // Vec2 old_main_dims = DimsFromRng2(UI_Rect(main_box)); + // old_main_dims.x = MaxF32(old_main_dims.x, 1); + // old_main_dims.y = MaxF32(old_main_dims.y, 1); + + + + // if (profiler->view_start_ns == 0 && profiler->view_end_ns == 0) + // { + // profiler->view_start_ns = NsFromSeconds(0); + // profiler->view_end_ns = NsFromSeconds(10); + // } + + + // f32 old_cursor_offset_px = frame->screen_cursor.x - UI_Rect(main_box).p0.x; + // i64 old_cursor_offset_ns = (old_cursor_offset_px / old_main_dims.x) * (profiler->view_end_ns - profiler->view_start_ns); + // i64 old_cursor_ns = profiler->view_start_ns + old_cursor_offset_ns; + + f32 cursor_px = frame->screen_cursor.x - UI_Rect(main_box).p0.x; + + b32 is_main_hot = UI_HotAbsolute(main_box); + // if (is_main_hot) { - VisZone *root_bfs = PushStruct(frame->arena, VisZone); - root_bfs->zone = zone_track->root_zone; - root_bfs->start_px = -Inf; - root_bfs->end_px = Inf; - root_bfs->start_ns = I64Min; - root_bfs->end_ns = I64Max; - root_bfs->id = zone_track->root_zone->id; - SllQueuePushN(first_bfs_vis_zone, last_bfs_vis_zone, root_bfs, next_bfs); + profiler->cursor_ns = (cursor_px * profiler->ns_per_px) + profiler->view_ns; } - for (VisZone *parent_vis = first_bfs_vis_zone; parent_vis; parent_vis = first_bfs_vis_zone) + // Snap view + if (!profiler->unsnap) { - SllQueuePopN(first_bfs_vis_zone, last_bfs_vis_zone, next_bfs); - V_Zone *parent = parent_vis->zone; + f64 snap_width_ns = NsFromSeconds(1.0 / 10.0); + profiler->target_view_ns = frame->time_ns - snap_width_ns; + profiler->target_ns_per_px = snap_width_ns / MaxF32(DimsFromRng2(UI_Rect(main_box)).x, 100); + } - // Process child zones + f32 prof_zoom_rate = 1.65; + f64 prof_lerp_rate = SaturateF64(frame->dt * 25); + + { + f64 zooms = UI_Presses(main_box, Button_WheelDown) - UI_Presses(main_box, Button_WheelUp); + if (zooms != 0) { - b32 reached_end_of_view = 0; - VisZone *left_vis = 0; - for (V_ZoneChunk *chunk = parent->first_chunk; chunk && !reached_end_of_view; chunk = chunk->next) + profiler->unsnap = 1; + } + f64 zoom_factor = PowF64(prof_zoom_rate, zooms); + profiler->target_ns_per_px *= zoom_factor; + profiler->target_view_ns = profiler->cursor_ns - (profiler->cursor_ns - profiler->target_view_ns) * zoom_factor; + if (zooms != 0) + { + profiler->last_zoom_delta = profiler->target_ns_per_px - profiler->ns_per_px; + profiler->last_view_delta = profiler->target_view_ns - profiler->view_ns; + } + f64 overshoot_factor = 0.01; + profiler->ns_per_px = LerpF64(profiler->ns_per_px, profiler->target_ns_per_px + profiler->last_zoom_delta * overshoot_factor, prof_lerp_rate); + profiler->view_ns = LerpF64(profiler->view_ns, profiler->target_view_ns + profiler->last_view_delta * overshoot_factor, prof_lerp_rate); + + if (profiler->last_zoom_delta < 0) + { + profiler->ns_per_px = MaxF64(profiler->ns_per_px, profiler->target_ns_per_px); + } + else if (profiler->last_zoom_delta > 0) + { + profiler->ns_per_px = MinF64(profiler->ns_per_px, profiler->target_ns_per_px); + } + + if (profiler->last_view_delta < 0) + { + profiler->view_ns = MaxF64(profiler->view_ns, profiler->target_view_ns); + } + else if (profiler->last_view_delta > 0) + { + profiler->view_ns = MinF64(profiler->view_ns, profiler->target_view_ns); + } + } + + + + + + + + + + + + + + + + + + + // if (profiler->is_dragging && !prev_frame->profiler.is_dragging) + // { + // profiler->drag_view_ns = profiler->view_ns; + // profiler->drag_cursor_px = cursor_px; + // } + + // if (profiler->is_dragging) + // { + // profiler->view_ns = profiler->drag_view_ns - ((cursor_px - profiler->drag_cursor_px) * profiler->ns_per_px); + // } + + + + + // FIXME: Recalc cursor after zoom + + + + + // if (is_main_hot) + { + profiler->cursor_ns = (cursor_px * profiler->ns_per_px) + profiler->view_ns; + } + + + + + + + // TODO: Drag in px units for sharper resolution + if (UI_Downs(main_box, Button_M2) || UI_Downs(main_box, Button_M3)) + { + profiler->drag_view_ns = profiler->view_ns; + profiler->drag_cursor_px = cursor_px; + } + + // Drag ruler + // TODO: When measuring stopped, lerp ruler closed + b32 is_measuring = 0; + if (UI_Downs(main_box, Button_M1)) + { + profiler->unsnap = 1; + profiler->ruler_start_ns = profiler->cursor_ns; + } + if (UI_Held(main_box, Button_M1)) + { + profiler->unsnap = 1; + is_measuring = 1; + profiler->target_ruler_opacity = 1; + } + else + { + profiler->target_ruler_opacity = 0; + } + profiler->ruler_opacity = LerpF32(profiler->ruler_opacity, profiler->target_ruler_opacity, prof_lerp_rate); + + // Drag view + if (UI_Held(main_box, Button_M2) || UI_Held(main_box, Button_M3)) + { + profiler->unsnap = 1; + profiler->view_ns = profiler->drag_view_ns - ((cursor_px - profiler->drag_cursor_px) * profiler->ns_per_px); + profiler->target_view_ns = profiler->view_ns; + } + + + + + + + + + // FIXME: Remove this + b32 do_break = frame->real_held_buttons[Button_B] && !prev_frame->real_held_buttons[Button_B]; + + + + + + + + // FIXME: Remove this + f64 view_range_len_px = DimsFromRng2(UI_Rect(main_box)).x; + f64 view_range_len_ns = view_range_len_px * profiler->ns_per_px; + + f64 view_start_ns = profiler->view_ns; + f64 view_end_ns = view_start_ns + view_range_len_ns; + + f64 view_start_px = view_start_ns / profiler->ns_per_px; + f64 view_end_px = view_end_ns / profiler->ns_per_px; + + + + + + + + + + + + + + + + + //- Generate visible zones + + Struct(VisZone) + { + VisZone *parent; + VisZone *next; + VisZone *next_bfs; + V_Zone *zone; + + u32 depth; + u64 id; + String name; + Vec4 color; + + f64 start_px; + f64 end_px; + + i64 start_ns; + i64 end_ns; + + u64 collapsed_count; + }; + + Struct(VisTrack) + { + VisTrack *next; + u64 id; + + u32 rows_count; + VisZone *first_zone; + VisZone *last_zone; + }; + + VisTrack *first_vis_track = 0; + VisTrack *last_vis_track = 0; + f32 zone_collapse_threshold_px = 10; + f32 zone_name_hide_threshold_px = UI_Top(FontSize) * 3; + f32 min_zone_width_px = 6; + ProfZoneDF("Generate visible zones") + { + for (V_ZoneTrack *zone_track = V.first_zone_track; zone_track; zone_track = zone_track->next) + { + // TODO: Ignore non-visible tracks + // TODO: Ignore non-visible depths + + VisTrack *vis_track = PushStruct(frame->arena, VisTrack); + SllQueuePush(first_vis_track, last_vis_track, vis_track); + vis_track->id = zone_track->id; + + VisZone *first_bfs_vis_zone = 0; + VisZone *last_bfs_vis_zone = 0; + + // Push root vis zone node to bfs { - i64 chunk_start_ns = chunk->zones[0].start_ns; - i64 chunk_end_ns = MinI64(chunk->zones[chunk->count - 1].end_ns, frame->time_ns); + VisZone *root_bfs = PushStruct(frame->arena, VisZone); + root_bfs->zone = zone_track->root_zone; + root_bfs->start_px = -Inf; + root_bfs->end_px = Inf; + root_bfs->start_ns = I64Min; + root_bfs->end_ns = I64Max; + root_bfs->id = zone_track->root_zone->id; + SllQueuePushN(first_bfs_vis_zone, last_bfs_vis_zone, root_bfs, next_bfs); + } - // i64 should_collapse_chunk = (chunk->end_ns - chunk->start_ns) / + for (VisZone *parent_vis = first_bfs_vis_zone; parent_vis; parent_vis = first_bfs_vis_zone) + { + SllQueuePopN(first_bfs_vis_zone, last_bfs_vis_zone, next_bfs); + V_Zone *parent = parent_vis->zone; - if (chunk_start_ns <= view_end_ns && chunk_end_ns >= view_start_ns) + // Process child zones { - // TODO: Binary search to find start within chunk - for (u64 chunk_zone_idx = 0; chunk_zone_idx < chunk->count && !reached_end_of_view; ++chunk_zone_idx) + b32 reached_end_of_view = 0; + VisZone *left_vis = 0; + for (V_ZoneChunk *chunk = parent->first_chunk; chunk && !reached_end_of_view; chunk = chunk->next) { - V_Zone *zone = &chunk->zones[chunk_zone_idx]; - if (zone->start_ns > view_end_ns) + i64 chunk_start_ns = chunk->zones[0].start_ns; + i64 chunk_end_ns = MinI64(chunk->zones[chunk->count - 1].end_ns, frame->time_ns); + + // i64 should_collapse_chunk = (chunk->end_ns - chunk->start_ns) / + + if (chunk_start_ns <= view_end_ns && chunk_end_ns >= view_start_ns) { - reached_end_of_view = 1; - } - else - { - i64 zone_start_ns = zone->start_ns; - i64 zone_end_ns = MinI64(zone->end_ns, frame->time_ns); - i64 zone_elapsed_ns = zone_end_ns - zone_start_ns; - i64 visual_zone_start_ns = MaxI64(zone_start_ns, profiler->view_ns); - i64 visual_zone_end_ns = MinI64(zone_end_ns, profiler->view_ns + view_range_len_ns); - - f64 visual_zone_start_px = visual_zone_start_ns / profiler->ns_per_px; - f64 visual_zone_end_px = visual_zone_end_ns / profiler->ns_per_px; - + // TODO: Binary search to find start within chunk + for (u64 chunk_zone_idx = 0; chunk_zone_idx < chunk->count && !reached_end_of_view; ++chunk_zone_idx) { - visual_zone_end_px = MaxF64(visual_zone_end_px, visual_zone_start_px + min_zone_width_px); + V_Zone *zone = &chunk->zones[chunk_zone_idx]; + if (zone->start_ns > view_end_ns) { - f32 overshoot = MaxF32(visual_zone_end_px - parent_vis->end_px, 0); - visual_zone_start_px -= overshoot; - visual_zone_end_px -= overshoot; + reached_end_of_view = 1; } - visual_zone_start_px = MaxF64(visual_zone_start_px, parent_vis->start_px); - if (left_vis) + else { - // visual_zone_start_px = MaxF64(visual_zone_start_px, CeilF64(left_vis->end_px)); - visual_zone_start_px = MaxF64(visual_zone_start_px, left_vis->end_px); - } - // visual_zone_start_px = MinF64(visual_zone_start_px, visual_zone_end_px - min_zone_width_px); - // visual_zone_start_px = MaxF64(visual_zone_start_px, parent_vis->start_px); + i64 zone_start_ns = zone->start_ns; + i64 zone_end_ns = MinI64(zone->end_ns, frame->time_ns); + i64 zone_elapsed_ns = zone_end_ns - zone_start_ns; + i64 visual_zone_start_ns = MaxI64(zone_start_ns, profiler->view_ns); + i64 visual_zone_end_ns = MinI64(zone_end_ns, profiler->view_ns + view_range_len_ns); - // visual_zone_end_px = MaxF64(visual_zone_end_px, visual_zone_start_px + min_zone_width_px); - // visual_zone_end_px = MinF64(visual_zone_end_px, parent_vis->end_px); - } + f64 visual_zone_start_px = visual_zone_start_ns / profiler->ns_per_px; + f64 visual_zone_end_px = visual_zone_end_ns / profiler->ns_per_px; - // Push vis zone - if (visual_zone_end_px <= (frame->time_ns / profiler->ns_per_px)) - { - b32 should_collapse_zone = (visual_zone_end_px - visual_zone_start_px) <= zone_collapse_threshold_px; - String zone_name = zone->name; - u64 zone_id = zone->id; - Vec4 zone_color = zone->color; - - // b32 should_collapse_zone = visual_zone_len_px <= zone_collapse_threshold_px; - // b32 should_collapse_zone = 0; - - // if (visual_zone_start_ns > view_end_ns) - // { - // reached_end_of_view = 1; - // } - - if (visual_zone_end_ns >= view_start_ns && visual_zone_start_ns <= view_end_ns) - { - VisZone *vis_zone = 0; - if ( - should_collapse_zone && - left_vis && - left_vis->collapsed_count > 0 && - ((zone_start_ns - left_vis->end_ns) / profiler->ns_per_px) < zone_collapse_threshold_px - ) { - vis_zone = left_vis; - } - else - { - vis_zone = PushStruct(frame->arena, VisZone); - vis_zone->zone = zone; - vis_zone->parent = parent_vis; - vis_zone->start_ns = zone_start_ns; - vis_zone->start_px = visual_zone_start_px; - vis_zone->name = zone_name; - vis_zone->id = zone_id; + visual_zone_end_px = MaxF64(visual_zone_end_px, visual_zone_start_px + min_zone_width_px); + { + f32 overshoot = MaxF32(visual_zone_end_px - parent_vis->end_px, 0); + visual_zone_start_px -= overshoot; + visual_zone_end_px -= overshoot; + } + visual_zone_start_px = MaxF64(visual_zone_start_px, parent_vis->start_px); + if (left_vis) + { + visual_zone_start_px = MaxF64(visual_zone_start_px, left_vis->end_px); + } + visual_zone_start_px = RoundF32(visual_zone_start_px); + visual_zone_end_px = RoundF32(visual_zone_end_px); } - SllQueuePush(vis_track->first_zone, vis_track->last_zone, vis_zone); - vis_zone->end_px = visual_zone_end_px; - vis_zone->end_ns = zone_end_ns; - vis_zone->color = zone_color; - vis_zone->depth = zone->depth; - vis_track->rows_count = MaxU32(vis_track->rows_count, vis_zone->depth); - - if (should_collapse_zone) + // Push vis zone + if (visual_zone_end_px <= (frame->time_ns / profiler->ns_per_px)) { - vis_zone->collapsed_count += zone->total_descendents_count + 1; - } + b32 should_collapse_zone = (visual_zone_end_px - visual_zone_start_px) <= zone_collapse_threshold_px; + String zone_name = zone->name; + u64 zone_id = zone->id; + Vec4 zone_color = zone->color; - left_vis = vis_zone; + // b32 should_collapse_zone = visual_zone_len_px <= zone_collapse_threshold_px; + // b32 should_collapse_zone = 0; - if (!should_collapse_zone) - { - SllQueuePushN(first_bfs_vis_zone, last_bfs_vis_zone, vis_zone, next_bfs); + // if (visual_zone_start_ns > view_end_ns) + // { + // reached_end_of_view = 1; + // } + + if (visual_zone_end_ns >= view_start_ns && visual_zone_start_ns <= view_end_ns) + { + VisZone *vis_zone = 0; + if ( + should_collapse_zone && + left_vis && + left_vis->collapsed_count > 0 && + ((zone_start_ns - left_vis->end_ns) / profiler->ns_per_px) < zone_collapse_threshold_px + ) + { + vis_zone = left_vis; + } + else + { + vis_zone = PushStruct(frame->arena, VisZone); + vis_zone->zone = zone; + vis_zone->parent = parent_vis; + vis_zone->start_ns = zone_start_ns; + vis_zone->start_px = visual_zone_start_px; + vis_zone->name = zone_name; + vis_zone->id = zone_id; + } + + SllQueuePush(vis_track->first_zone, vis_track->last_zone, vis_zone); + vis_zone->end_px = visual_zone_end_px; + vis_zone->end_ns = zone_end_ns; + vis_zone->color = zone_color; + vis_zone->depth = zone->depth; + vis_track->rows_count = MaxU32(vis_track->rows_count, vis_zone->depth); + + if (should_collapse_zone) + { + vis_zone->collapsed_count += zone->total_descendents_count + 1; + } + + left_vis = vis_zone; + + if (!should_collapse_zone) + { + SllQueuePushN(first_bfs_vis_zone, last_bfs_vis_zone, vis_zone, next_bfs); + } + } } } } @@ -4212,548 +4313,549 @@ void V_TickForever(WaveLaneCtx *lane) } } } - } - } - Vec4 profiler_color = theme.col.window_bg; - f32 profiler_opacity = TweakFloat("Profiler opacity", 1, 0, 1); + // Vec4 profiler_color = theme.col.window_bg; + Vec4 profiler_color = theme.col.window_bg; + f32 profiler_opacity = TweakFloat("Profiler opacity", 1, 0, 1); - Vec4 timeline_cursor_color = theme.col.window_bd; - timeline_cursor_color.a = UI_Hot(main_box) * 0.75; - UI_Size timeline_cursor_width = UI_Fnt(0.15, 1); + Vec4 timeline_cursor_color = theme.col.window_bd; + timeline_cursor_color.a = UI_Hot(main_box) * 0.75; + UI_Size timeline_cursor_width = UI_Fnt(0.15, 1); - UI_Key hovered_zone_box = Zi; - VisZone *hovered_zone = 0; - f64 ruler_len_ns = 0; + UI_Key hovered_zone_box = Zi; + VisZone *hovered_zone = 0; + f64 ruler_len_ns = 0; - Vec4 main_color_sampled = profiler_color; - main_color_sampled.r *= 0.75; - main_color_sampled.g *= 0.75; - main_color_sampled.b *= 0.75; + Vec4 main_color_sampled = profiler_color; + main_color_sampled.r *= 0.75; + main_color_sampled.g *= 0.75; + main_color_sampled.b *= 0.75; - Vec4 main_color_unsampled = main_color_sampled; - main_color_unsampled.r *= 0.85; - main_color_unsampled.g *= 0.85; - main_color_unsampled.b *= 0.85; + Vec4 main_color_unsampled = main_color_sampled; + main_color_unsampled.r *= 0.85; + main_color_unsampled.g *= 0.85; + main_color_unsampled.b *= 0.85; - Vec4 collapsed_line_color = SrgbFromHsv(64, 1, 0.5); - Vec4 collapsed_text_color = SrgbFromHsv(64, 1, 0.75); + Vec4 collapsed_line_color = SrgbFromHsv(64, 1, 0.5); + Vec4 collapsed_text_color = SrgbFromHsv(64, 1, 0.75); - // main_color_sampled.g += 0.05; + // main_color_sampled.g += 0.05; - UI_SetNext(Parent, vis_screen_box); - UI_SetNext(BorderColor, theme.col.window_bd); - UI_SetNext(BorderSize, 2); - UI_SetNext(BackgroundColor, profiler_color); - UI_SetNext(Rounding, UI_Rpx(10 * theme.rounding)); - UI_SetNext(Height, profiler_height); - UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); - UI_SetNext(Opacity, profiler_opacity); - UI_SetNext(Parent, panel->contents_box); - UI_PushDF(Width, UI_Grow(1, 0)) - UI_PushDF(Parent, UI_BuildColumnEx(profiler_box)) - UI_PushDF(Parent, UI_BuildRow()) - { - UI_BuildSpacer(window_padding, Axis_X); - UI_PushDF(Parent, UI_BuildColumn()) - { - //- Header - // UI_PushDF(BackgroundColor, Color_Red) - // UI_SetNext(BorderColor, Color_Red); - // UI_SetNext(BorderSize, 1); - // UI_SetNext(ChildAlignment, UI_Region_Center); - // UI_PushDF(Height, header_height) - // UI_PushDF(Parent, UI_BuildBoxEx(UI_KeyF("profiler header"))) - // { - // UI_PushDF(TextColor, theme.col.hint) - // UI_BuildLabelF("Profiler"); - // } - - UI_BuildSpacer(window_padding, Axis_Y); - - UI_BuildDivider(UI_Px(1, 1), theme.col.divider, Axis_Y); - - //- Graph - // UI_SetNext(BackgroundColor, Color_Cyan); - UI_SetNext(Height, graph_height); - UI_PushDF(Parent, UI_BuildBoxEx(profiler_graph_box)) + UI_SetNext(Parent, vis_screen_box); + UI_SetNext(BorderColor, theme.col.window_bd); + UI_SetNext(BorderSize, 2); + UI_SetNext(BackgroundColor, profiler_color); + UI_SetNext(Rounding, UI_Rpx(10 * theme.rounding)); + UI_SetNext(Height, profiler_height); + UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); + UI_SetNext(Opacity, profiler_opacity); + UI_SetNext(Parent, panel->contents_box); + UI_PushDF(Width, UI_Grow(1, 0)) + UI_PushDF(Parent, UI_BuildColumnEx(profiler_box)) + UI_PushDF(Parent, UI_BuildRow()) { - } - // UI_BuildDivider(UI_Px(1, 1), theme.col.divider, Axis_Y); - - UI_BuildDivider(UI_Px(1, 1), theme.col.divider, Axis_Y); - - UI_BuildSpacer(window_padding, Axis_Y); - - //- Main area - UI_SetNext(BackgroundColor, main_color_unsampled); - UI_SetNext(Height, main_height); - UI_SetNext(Rounding, UI_Rpx(5 * theme.rounding)); - // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_CaptureThroughChildren | UI_BoxFlag_Scissor); - UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_CaptureThroughChildren); - // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); - UI_PushDF(Parent, UI_BuildColumnEx(main_box)) - { - //- Active area background - UI_PushDF(Opacity, 0.75) + UI_BuildSpacer(window_padding, Axis_X); + UI_PushDF(Parent, UI_BuildColumn()) { - f64 aabg_left_ns = MaxF64(0, profiler->view_ns); - f64 aabg_right_ns = MinF64(frame->time_ns, view_end_ns); + //- Header + // UI_PushDF(BackgroundColor, Color_Red) + // UI_SetNext(BorderColor, Color_Red); + // UI_SetNext(BorderSize, 1); + // UI_SetNext(ChildAlignment, UI_Region_Center); + // UI_PushDF(Height, header_height) + // UI_PushDF(Parent, UI_BuildBoxEx(UI_KeyF("profiler header"))) + // { + // UI_PushDF(TextColor, theme.col.hint) + // UI_BuildLabelF("Profiler"); + // } - f64 aabg_len_ns = aabg_right_ns - aabg_left_ns; - f64 aabg_len_px = aabg_len_ns / profiler->ns_per_px; + UI_BuildSpacer(window_padding, Axis_Y); - f64 aabg_cursor_offset_px = (aabg_left_ns - profiler->view_ns) / profiler->ns_per_px; - f32 aabg_offset_px = (aabg_left_ns - profiler->view_ns) / profiler->ns_per_px; - Vec2 aabg_cursor_pos = VEC2(aabg_cursor_offset_px, 0); - Vec2 aabg_pos = VEC2(aabg_offset_px, 0); - UI_Size aabg_width = UI_Px(aabg_len_px, 1); + UI_BuildDivider(UI_Px(1, 1), theme.col.divider, Axis_Y); - Vec4 aabg_color = main_color_sampled; - - // Ruler rect + //- Graph + // UI_SetNext(BackgroundColor, Color_Cyan); + UI_SetNext(Height, graph_height); + UI_PushDF(Parent, UI_BuildBoxEx(profiler_graph_box)) { - // UI_SetNext(BorderSize, 1); - // UI_SetNext(BorderColor, Color_White); - UI_SetNext(Width, aabg_width); - UI_SetNext(FloatingPos, aabg_pos); - UI_SetNext(BackgroundColor, aabg_color); - UI_SetNext(Flags, UI_BoxFlag_Floating); - UI_BuildBoxEx(UI_KeyF("active area background")); } - } + // UI_BuildDivider(UI_Px(1, 1), theme.col.divider, Axis_Y); - //- Zone tracks - ProfZoneDF("Build zones") - { - for (VisTrack *vis_track = first_vis_track; vis_track; vis_track = vis_track->next) + UI_BuildDivider(UI_Px(1, 1), theme.col.divider, Axis_Y); + + UI_BuildSpacer(window_padding, Axis_Y); + + //- Main area + UI_SetNext(BackgroundColor, main_color_unsampled); + UI_SetNext(Height, main_height); + UI_SetNext(Rounding, UI_Rpx(5 * theme.rounding)); + // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_CaptureThroughChildren | UI_BoxFlag_Scissor); + UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_CaptureThroughChildren); + // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); + UI_PushDF(Parent, UI_BuildColumnEx(main_box)) { - UI_SetNext(Height, track_height); - UI_PushDF(Tag, HashF("vis track %F", FmtUint(vis_track->id))) - UI_PushDF(Parent, UI_BuildColumn()) + //- Active area background + UI_PushDF(Opacity, 0.75) { - //- Zone rows - UI_Key *zone_row_boxes = PushStructs(frame->arena, UI_Key, vis_track->rows_count); - for (u64 zone_row_box_idx = 0; zone_row_box_idx < vis_track->rows_count; ++zone_row_box_idx) + f64 aabg_left_ns = MaxF64(0, profiler->view_ns); + f64 aabg_right_ns = MinF64(frame->time_ns, view_end_ns); + + f64 aabg_len_ns = aabg_right_ns - aabg_left_ns; + f64 aabg_len_px = aabg_len_ns / profiler->ns_per_px; + + f64 aabg_cursor_offset_px = (aabg_left_ns - profiler->view_ns) / profiler->ns_per_px; + f32 aabg_offset_px = (aabg_left_ns - profiler->view_ns) / profiler->ns_per_px; + Vec2 aabg_cursor_pos = VEC2(aabg_cursor_offset_px, 0); + Vec2 aabg_pos = VEC2(aabg_offset_px, 0); + UI_Size aabg_width = UI_Px(aabg_len_px, 1); + + Vec4 aabg_color = main_color_sampled; + + // Ruler rect { - UI_Key zone_row_box = UI_KeyF("zone row %F", FmtUint(zone_row_box_idx)); - zone_row_boxes[zone_row_box_idx] = zone_row_box; - // UI_SetNext(Height, zone_height); - UI_SetNext(Height, zone_height); - UI_PushDF(Parent, UI_BuildRowEx(zone_row_box)) - { - } + // UI_SetNext(BorderSize, 1); + // UI_SetNext(BorderColor, Color_White); + UI_SetNext(Width, aabg_width); + UI_SetNext(FloatingPos, aabg_pos); + UI_SetNext(BackgroundColor, aabg_color); + UI_SetNext(Flags, UI_BoxFlag_Floating); + UI_BuildBoxEx(UI_KeyF("active area background")); } + } - //- Zones - for (VisZone *zone = vis_track->first_zone; zone; zone = zone->next) + //- Zone tracks + ProfZoneDF("Build zones") + { + for (VisTrack *vis_track = first_vis_track; vis_track; vis_track = vis_track->next) { - UI_Key zone_row_box = zone_row_boxes[zone->depth - 1]; - b32 can_hover = profiler->unsnap; - - UI_Key zone_box = UI_KeyF("Zone %F", FmtUint(zone->id)); - b32 is_hovered = can_hover && UI_HoveredAbsolute(zone_box) && UI_HotAbsolute(main_box); - if (is_hovered) + UI_SetNext(Height, track_height); + UI_PushDF(Tag, HashF("vis track %F", FmtUint(vis_track->id))) + UI_PushDF(Parent, UI_BuildColumn()) { - hovered_zone_box = zone_box; - hovered_zone = zone; + // UI_BuildSpacer(UI_Px(10, 0), Axis_Y); + // UI_BuildSpacer(UI_Grow(1, 0), Axis_Y); - - // FIXME: Remove this - b32 raah = UI_HotAbsolute(main_box); - - } - f32 zone_hovered = can_hover * UI_Hovered(zone_box) * UI_Hot(main_box); - - f64 zone_offset_px = zone->start_px - view_start_px; - f64 zone_len_px = zone->end_px - zone->start_px; - - Vec2 zone_pos = VEC2(zone_offset_px, 0); - UI_Size zone_width = UI_Px(zone_len_px, 1); - - Vec4 zone_color = zone->color; - Vec4 zone_color_bd = zone_color; - - zone_color_bd = MulVec4(zone_color_bd, 0.65); - zone_color_bd.a = 1; - - Vec4 zone_text_color = UI_Top(TextColor); - zone_text_color.a *= 0.75; - - Vec4 zone_line_color = Zi; - - // b32 is_collapsed = zone->is_collapsed; - b32 is_collapsed = zone->collapsed_count > 0; - // b32 is_collapsed = zone->collapsed_count > 0 || zone_len_px <= min_zone_width_px; - - // UI_Size collapsed_line_size = UI_Px(2, 1); - UI_Size collapsed_line_size = UI_Px(1.5, 1); - // Vec4 collapsed_text_color = theme.col.positive; - // Vec4 collapsed_line_color = theme.col.negative; - // Vec4 collapsed_line_color = SrgbFromHsv(TweakFloat("RAAAAAAAAAH H", 100, 0, 360), TweakFloat("RAAAAAAAAAH S", 0.50, 0, 1), TweakFloat("RAAAAAAAAAH V", 1.00, 0, 1)); - // Vec4 collapsed_line_color = Color_Black; - // Vec4 collapsed_line_color = theme.col.hint; - // Vec4 collapsed_text_color = theme.col.positive; - // Vec4 collapsed_text_color = SrgbFromHsv(TweakFloat("RAAAAAAAAAH H", 100, 0, 360), TweakFloat("RAAAAAAAAAH S", 0.50, 0, 1), TweakFloat("RAAAAAAAAAH V", 1.00, 0, 1)); - - // Vec4 collapsed_line_color = SrgbFromHsv(32, 1, 0.5); - // Vec4 collapsed_text_color = SrgbFromHsv(32, 1, 1); - - // Vec4 collapsed_line_color = SrgbFromHsv(64, 1, 0.5); - // Vec4 collapsed_text_color = SrgbFromHsv(64, 1, 0.75); - - // collapsed_text_color = LerpSrgb(collapsed_text_color, Color_White, zone_hovered); - // collapsed_line_color = LerpSrgb(collapsed_line_color, Color_White, zone_hovered); - - - - - - String zone_text = zone->name; - if (is_collapsed) - { - zone_text = StringF(frame->arena, "%F", FmtUint(zone->collapsed_count)); - zone_text_color = collapsed_text_color; - zone_line_color = collapsed_line_color; - - // zone_color = VEC4(0, 0, 0, 0); - // zone_color = VEC4(1, 0, 1, 1); - // zone_color = VEC4(0.15, 0.15, 0.15, 0.5); - // zone_color = VEC4(0.20, 0.20, 0.20, 0.5); - - - zone_color = VEC4(0.25, 0.25, 0.25, 0.5); - - - // if (zone->collapsed_count > 1) - // { - // zone_color = VEC4(0.25, 0.25, 0.25, 0.5); - // } - // else - // { - // zone_color = Color_White; - // } - - - - // zone_color = VEC4(0.4, 0.4, 0.4, 0.5); - zone_color_bd = VEC4(0, 0, 0, 0); - - // zone_color = LerpSrgb(zone_color, Color_Transparent, zone_hovered); - - } - - if (zone_len_px < zone_name_hide_threshold_px) - { - zone_text.len = 0; - } - - zone_color_bd = LerpSrgb(zone_color_bd, Color_White, zone_hovered * !is_collapsed); - zone_text_color = LerpSrgb(zone_text_color, Color_White, zone_hovered); - zone_line_color = LerpSrgb(zone_line_color, Color_White, zone_hovered); - - - f32 collapsed_zones_per_px = zone->collapsed_count / zone_len_px; - // f32 zag_intensity = 50 / collapsed_zones_per_px; - // zag_intensity = MaxF32(zag_intensity, 5); - - // f32 top_zag_information_density = 10; - f32 top_zag_information_density = zone_collapse_threshold_px; - // f32 zag_intensity = SmoothstepF32(0, top_zag_information_density, collapsed_zones_per_px); - // f32 zag_intensity = 1; - f32 zag_intensity = 0.3; - // f32 zag_intensity = TweakFloat("RAAAAAAAAAAAAAH", 1, 0, 1); - - f32 period_max = 20; - f32 period_min = 5; - f32 zag_period = LerpF32(period_max, period_min, zag_intensity); - - f32 amplitude_max = UI_Top(FontSize) * 0.15; - f32 amplitude_min = UI_Top(FontSize) * 0.05; - f32 zag_amplitude = LerpF32(amplitude_max, amplitude_min, zag_intensity); - - UI_SetNext(Width, zone_width); - UI_SetNext(Height, UI_Grow(1, 0)); - UI_SetNext(BackgroundColor, zone_color); - UI_SetNext(BorderColor, zone_color_bd); - UI_SetNext(BorderSize, 1); - UI_SetNext(FloatingPos, zone_pos); - UI_SetNext(ChildAlignment, UI_Region_Center); - UI_SetNext( - Flags, - UI_BoxFlag_Floating | - UI_BoxFlag_DontClampFloatingX | - UI_BoxFlag_DontClampFloatingY | - UI_BoxFlag_CaptureMouse | - UI_BoxFlag_Scissor - ); - UI_SetNext(Parent, zone_row_box); - // UI_PushDF(OrFlags, UI_Top(OrFlags) | (UI_BoxFlag_DontTruncateText * !!(is_collapsed))) - // UI_PushDF(OmitFlags, UI_Top(OmitFlags) | (UI_BoxFlag_Scissor * !!(is_collapsed))) - // UI_PushDF(ZagPeriod, UI_Top(FontSize) * 1) - // UI_PushDF(ZagPeriod, (collapsed_zones_per_px * UI_Top(FontSize)) * 0.1) - // UI_PushDF(ZagPeriod, collapsed_zones_per_px / (UI_Top(FontSize) * 1)) - UI_PushDF(ZagPeriod, zag_period) - UI_PushDF(ZagThickness, 1.5) - UI_PushDF(ZagRoundness, 1) - UI_PushDF(ZagAmplitude, zag_amplitude) - UI_PushDF(Parent, UI_BuildRowEx(zone_box)) - { - if (is_collapsed) + //- Zone rows + UI_Key *zone_row_boxes = PushStructs(frame->arena, UI_Key, vis_track->rows_count); + for (u64 zone_row_box_idx = 0; zone_row_box_idx < vis_track->rows_count; ++zone_row_box_idx) { - // Left zag - UI_SetNext(Width, UI_Grow(1, 0)); - UI_SetNext(ZagColor, zone_line_color); - UI_BuildBox(); - } - else - { - UI_SetNext(Width, UI_Px(3, 1)); - UI_BuildBox(); + UI_Key zone_row_box = UI_KeyF("zone row %F", FmtUint(zone_row_box_idx)); + zone_row_boxes[zone_row_box_idx] = zone_row_box; + // UI_SetNext(Height, zone_height); + UI_SetNext(Height, zone_height); + UI_PushDF(Parent, UI_BuildRowEx(zone_row_box)) + { + } } - if (zone_text.len > 0) + //- Zones + for (VisZone *zone = vis_track->first_zone; zone; zone = zone->next) { - // Zone name - // if (zone_len_px > zone_collapse_threshold_px * 3) + UI_Key zone_row_box = zone_row_boxes[zone->depth - 1]; + b32 can_hover = profiler->unsnap; + + UI_Key zone_box = UI_KeyF("Zone %F", FmtUint(zone->id)); + b32 is_hovered = can_hover && UI_HoveredAbsolute(zone_box) && UI_HotAbsolute(main_box); + if (is_hovered) + { + hovered_zone_box = zone_box; + hovered_zone = zone; + } + f32 zone_hovered = can_hover * UI_Hovered(zone_box) * UI_Hot(main_box); + + f64 zone_offset_px = zone->start_px - view_start_px; + f64 zone_len_px = zone->end_px - zone->start_px; + + Vec2 zone_pos = VEC2(zone_offset_px, 0); + UI_Size zone_width = UI_Px(zone_len_px, 1); + + Vec4 zone_color = zone->color; + Vec4 zone_color_bd = zone_color; + + zone_color_bd = MulVec4(zone_color_bd, 0.65); + zone_color_bd.a = 1; + + Vec4 zone_text_color = UI_Top(TextColor); + zone_text_color.a *= 0.75; + + Vec4 zone_line_color = Zi; + + // b32 is_collapsed = zone->is_collapsed; + b32 is_collapsed = zone->collapsed_count > 0; + // b32 is_collapsed = zone->collapsed_count > 0 || zone_len_px <= min_zone_width_px; + + // UI_Size collapsed_line_size = UI_Px(2, 1); + UI_Size collapsed_line_size = UI_Px(1.5, 1); + // Vec4 collapsed_text_color = theme.col.positive; + // Vec4 collapsed_line_color = theme.col.negative; + // Vec4 collapsed_line_color = SrgbFromHsv(TweakFloat("RAAAAAAAAAH H", 100, 0, 360), TweakFloat("RAAAAAAAAAH S", 0.50, 0, 1), TweakFloat("RAAAAAAAAAH V", 1.00, 0, 1)); + // Vec4 collapsed_line_color = Color_Black; + // Vec4 collapsed_line_color = theme.col.hint; + // Vec4 collapsed_text_color = theme.col.positive; + // Vec4 collapsed_text_color = SrgbFromHsv(TweakFloat("RAAAAAAAAAH H", 100, 0, 360), TweakFloat("RAAAAAAAAAH S", 0.50, 0, 1), TweakFloat("RAAAAAAAAAH V", 1.00, 0, 1)); + + // Vec4 collapsed_line_color = SrgbFromHsv(32, 1, 0.5); + // Vec4 collapsed_text_color = SrgbFromHsv(32, 1, 1); + + // Vec4 collapsed_line_color = SrgbFromHsv(64, 1, 0.5); + // Vec4 collapsed_text_color = SrgbFromHsv(64, 1, 0.75); + + // collapsed_text_color = LerpSrgb(collapsed_text_color, Color_White, zone_hovered); + // collapsed_line_color = LerpSrgb(collapsed_line_color, Color_White, zone_hovered); + + + + + + String zone_text = zone->name; + if (is_collapsed) + { + zone_text = StringF(frame->arena, "%F", FmtUint(zone->collapsed_count)); + zone_text_color = collapsed_text_color; + zone_line_color = collapsed_line_color; + + // zone_color = VEC4(0, 0, 0, 0); + // zone_color = VEC4(1, 0, 1, 1); + // zone_color = VEC4(0.15, 0.15, 0.15, 0.5); + // zone_color = VEC4(0.20, 0.20, 0.20, 0.5); + + + zone_color = VEC4(0.25, 0.25, 0.25, 0.5); + + + // if (zone->collapsed_count > 1) + // { + // zone_color = VEC4(0.25, 0.25, 0.25, 0.5); + // } + // else + // { + // zone_color = Color_White; + // } + + + + // zone_color = VEC4(0.4, 0.4, 0.4, 0.5); + zone_color_bd = VEC4(0, 0, 0, 0); + + // zone_color = LerpSrgb(zone_color, Color_Transparent, zone_hovered); + + } + + if (zone_len_px < zone_name_hide_threshold_px) + { + zone_text.len = 0; + } + + zone_color_bd = LerpSrgb(zone_color_bd, Color_White, zone_hovered * !is_collapsed); + zone_text_color = LerpSrgb(zone_text_color, Color_White, zone_hovered); + zone_line_color = LerpSrgb(zone_line_color, Color_White, zone_hovered); + + + f32 collapsed_zones_per_px = zone->collapsed_count / zone_len_px; + // f32 zag_intensity = 50 / collapsed_zones_per_px; + // zag_intensity = MaxF32(zag_intensity, 5); + + // f32 top_zag_information_density = 10; + f32 top_zag_information_density = zone_collapse_threshold_px; + // f32 zag_intensity = SmoothstepF32(0, top_zag_information_density, collapsed_zones_per_px); + // f32 zag_intensity = 1; + f32 zag_intensity = 0.3; + // f32 zag_intensity = TweakFloat("RAAAAAAAAAAAAAH", 1, 0, 1); + + f32 period_max = 20; + f32 period_min = 5; + f32 zag_period = LerpF32(period_max, period_min, zag_intensity); + + f32 amplitude_max = UI_Top(FontSize) * 0.15; + f32 amplitude_min = UI_Top(FontSize) * 0.05; + f32 zag_amplitude = LerpF32(amplitude_max, amplitude_min, zag_intensity); + + UI_SetNext(Width, zone_width); + UI_SetNext(Height, UI_Grow(1, 0)); + UI_SetNext(BackgroundColor, zone_color); + UI_SetNext(BorderColor, zone_color_bd); + UI_SetNext(BorderSize, 1); + UI_SetNext(FloatingPos, zone_pos); + UI_SetNext(ChildAlignment, UI_Region_Center); + UI_SetNext( + Flags, + UI_BoxFlag_Floating | + UI_BoxFlag_DontClampFloatingX | + UI_BoxFlag_DontClampFloatingY | + UI_BoxFlag_CaptureMouse | + UI_BoxFlag_Scissor + ); + UI_SetNext(Parent, zone_row_box); + // UI_PushDF(OrFlags, UI_Top(OrFlags) | (UI_BoxFlag_DontTruncateText * !!(is_collapsed))) + // UI_PushDF(OmitFlags, UI_Top(OmitFlags) | (UI_BoxFlag_Scissor * !!(is_collapsed))) + // UI_PushDF(ZagPeriod, UI_Top(FontSize) * 1) + // UI_PushDF(ZagPeriod, (collapsed_zones_per_px * UI_Top(FontSize)) * 0.1) + // UI_PushDF(ZagPeriod, collapsed_zones_per_px / (UI_Top(FontSize) * 1)) + UI_PushDF(ZagPeriod, zag_period) + UI_PushDF(ZagThickness, 1.5) + UI_PushDF(ZagRoundness, 1) + UI_PushDF(ZagAmplitude, zag_amplitude) + UI_PushDF(Parent, UI_BuildRowEx(zone_box)) { if (is_collapsed) { - UI_SetNext(Width, UI_Shrink(0, 1)); - UI_SetNext(ChildAlignment, UI_Region_Center); - } - else - { - UI_SetNext(ChildAlignment, UI_Region_Left); - UI_SetNext(Width, UI_Grow(1, 0)); - } - UI_SetNext(FontSize, UI_Top(FontSize) * theme.h5); - UI_SetNext(TextColor, zone_text_color); - UI_SetNext(Text, zone_text); - UI_SetNext(Flags, UI_BoxFlag_DrawText | UI_BoxFlag_DontTruncateText); - UI_BuildRow(); - } - - // Right collapsed lines - if (is_collapsed) - { - // Right zag - { + // Left zag UI_SetNext(Width, UI_Grow(1, 0)); UI_SetNext(ZagColor, zone_line_color); UI_BuildBox(); } - } - } - } - } - } - } - } - - - - - - //- Ruler - UI_PushDF(Opacity, profiler->ruler_opacity) - { - { - f64 ruler_left_ns = MinF64(profiler->ruler_start_ns, profiler->cursor_ns); - f64 ruler_right_ns = MaxF64(profiler->ruler_start_ns, profiler->cursor_ns); - ruler_left_ns = ClampF64(ruler_left_ns, view_start_ns, view_end_ns); - ruler_right_ns = ClampF64(ruler_right_ns, view_start_ns, view_end_ns); - - ruler_len_ns = ruler_right_ns - ruler_left_ns; - f64 ruler_len_px = ruler_len_ns / profiler->ns_per_px; - - f64 ruler_cursor_offset_px = (profiler->ruler_start_ns - profiler->view_ns) / profiler->ns_per_px; - f32 ruler_offset_px = (ruler_left_ns - profiler->view_ns) / profiler->ns_per_px; - Vec2 ruler_cursor_pos = VEC2(ruler_cursor_offset_px, 0); - Vec2 ruler_pos = VEC2(ruler_offset_px, 0); - UI_Size ruler_width = UI_Px(ruler_len_px, 1); - - // Vec4 ruler_color = VEC4(0.25, 0.25, 0.25, 0.25); - Vec4 ruler_color = VEC4(0.20, 0.20, 0.20, 0.25); - - // Ruler rect - { - // UI_SetNext(BorderSize, 1); - // UI_SetNext(BorderColor, Color_White); - // UI_PushDF(Opacity, profiler->ruler_opacity) - UI_SetNext(Width, ruler_width); - UI_SetNext(FloatingPos, ruler_pos); - UI_SetNext(BackgroundColor, ruler_color); - UI_SetNext(Flags, UI_BoxFlag_Floating); - UI_BuildBoxEx(UI_KeyF("ruler")); - } - - // Ruler cursor - { - // UI_SetNext(BorderSize, 1); - // UI_SetNext(BorderColor, Color_White); - UI_SetNext(BackgroundColor, timeline_cursor_color); - UI_SetNext(Width, timeline_cursor_width); - UI_SetNext(FloatingPos, ruler_cursor_pos); - UI_SetNext(Anchor, UI_Region_Top); - UI_SetNext(Flags, UI_BoxFlag_Floating); - UI_BuildBoxEx(UI_KeyF("ruler cursor")); - } - } - } - - //- Timeline cursor - { - // Vec2 timeline_cursor_pos = VEC2(old_cursor_offset_px, 0); - Vec2 timeline_cursor_pos = VEC2((profiler->cursor_ns - profiler->view_ns) / profiler->ns_per_px, 0); - - UI_SetNext(Width, timeline_cursor_width); - UI_SetNext(Height, UI_Grow(1, 0)); - UI_SetNext(BackgroundColor, timeline_cursor_color); - UI_SetNext(FloatingPos, timeline_cursor_pos); - UI_SetNext(Flags, UI_BoxFlag_Floating); - UI_BuildBox(); - } - - //- Timeline tooltop - { - b32 show_ruler_time = is_measuring && ruler_len_ns != 0; - b32 show_hovered_zone = hovered_zone != 0; - if (show_ruler_time || show_hovered_zone) - { - UI_Key tooltip_box = UI_KeyF("tooltip"); - Vec2 tooltip_pos = SubVec2(frame->screen_cursor, UI_Anchor(main_box)); - tooltip_pos = AddVec2(tooltip_pos, theme.tooltip_offset_px); - f32 tooltip_opacity = 0.95; - - UI_SetNext(Opacity, tooltip_opacity); - UI_SetNext(BackgroundColor, theme.col.window_bg); - UI_SetNext(BorderColor, theme.col.window_bd); - UI_SetNext(Rounding, UI_Rpx(theme.rounding * 5)); - UI_SetNext(BorderSize, 1); - UI_SetNext(Anchor, UI_Region_TopLeft); - UI_SetNext(FloatingPos, tooltip_pos); - UI_SetNext(Flags, UI_BoxFlag_Floating); - UI_PushDF(Width, UI_Shrink(0, 1)) - UI_PushDF(Height, UI_Shrink(0, 1)) - UI_PushDF(Parent, UI_BuildRowEx(tooltip_box)) - { - UI_BuildSpacer(window_padding, Axis_X); - - UI_SetNext(Width, UI_Shrink(0, 1)); - UI_SetNext(Height, UI_Shrink(0, 1)); - UI_PushDF(Parent, UI_BuildColumn()) - { - UI_BuildSpacer(window_padding, Axis_Y); - - if (show_ruler_time) - { - // UI_SetNext(TextColor, theme.col.positive); - // UI_SetNext(TextColor, theme.col.button_active); - - UI_SetNext(TextColor, theme.col.button_selected); - UI_BuildLabelF("%F", FmtTimeNs(ruler_len_ns, .p = 2)); - - // UI_SetNext(TextColor, theme.col.button_selected); - // UI_BuildLabelF("Ruler"); - - if (show_hovered_zone) - { - UI_BuildSpacer(minor_padding, Axis_Y); - UI_BuildSpacer(minor_padding, Axis_Y); - UI_BuildDivider(UI_Px(1, 0), theme.col.divider, Axis_Y); - UI_BuildSpacer(minor_padding, Axis_Y); - } - } - - if (show_hovered_zone) - { - VisZone *zone = hovered_zone; - UI_PushDF(Parent, UI_BuildRow()) - { - i64 elapsed_ns = zone->end_ns - zone->start_ns; - UI_PushDF(Parent, UI_BuildColumn()) - { - UI_BuildLabelF("%F ", FmtTimeNs(elapsed_ns, .p = 2)); - - if (zone->collapsed_count > 0) + else { - UI_PushDF(Parent, UI_BuildRow()) + UI_SetNext(Width, UI_Px(3, 1)); + UI_BuildBox(); + } + + if (zone_text.len > 0) + { + // Zone name + // if (zone_len_px > zone_collapse_threshold_px * 3) { - if (zone->collapsed_count == 1) + if (is_collapsed) { - // // UI_BuildLabelF(" (%F)", FmtUint(zone->collapsed_count)); - // UI_SetNext(TextColor, theme.col.hint); - // UI_BuildLabelF("%F", FmtString(zone->name)); - - // UI_SetNext(TextColor, collapsed_text_color); - // UI_BuildLabelF(" (Too small to display)"); - - UI_SetNext(TextColor, collapsed_text_color); - UI_BuildLabelF("1 zone too small to display: "); - UI_SetNext(TextColor, theme.col.hint); - UI_BuildLabelF("%F", FmtString(zone->name)); + UI_SetNext(Width, UI_Shrink(0, 1)); + UI_SetNext(ChildAlignment, UI_Region_Center); } else { - // UI_SetNext(TextColor, theme.col.hint); - // UI_BuildLabelF("Zones too small to display"); - - // UI_SetNext(TextColor, collapsed_text_color); - // UI_BuildLabelF(" (%F)", FmtUint(zone->collapsed_count)); - - - // UI_SetNext(TextColor, theme.col.hint); - UI_SetNext(TextColor, collapsed_text_color); - UI_BuildLabelF("%F zones too small to display", FmtUint(zone->collapsed_count)); + UI_SetNext(ChildAlignment, UI_Region_Left); + UI_SetNext(Width, UI_Grow(1, 0)); } + UI_SetNext(FontSize, UI_Top(FontSize) * theme.h5); + UI_SetNext(TextColor, zone_text_color); + UI_SetNext(Text, zone_text); + UI_SetNext(Flags, UI_BoxFlag_DrawText | UI_BoxFlag_DontTruncateText); + UI_BuildRow(); + } + + // Right collapsed lines + if (is_collapsed) + { + UI_SetNext(Width, UI_Grow(1, 0)); + UI_SetNext(ZagColor, zone_line_color); + UI_BuildBox(); + } + else + { + UI_SetNext(Width, UI_Px(3, 1)); + UI_BuildBox(); } - } - else - { - UI_SetNext(TextColor, theme.col.hint); - UI_BuildLabelF("%F", FmtString(zone->name)); } } } } - - UI_BuildSpacer(window_padding, Axis_Y); } - UI_BuildSpacer(window_padding, Axis_X); + } + + + + + + //- Ruler + UI_PushDF(Opacity, profiler->ruler_opacity) + { + { + f64 ruler_left_ns = MinF64(profiler->ruler_start_ns, profiler->cursor_ns); + f64 ruler_right_ns = MaxF64(profiler->ruler_start_ns, profiler->cursor_ns); + ruler_left_ns = ClampF64(ruler_left_ns, view_start_ns, view_end_ns); + ruler_right_ns = ClampF64(ruler_right_ns, view_start_ns, view_end_ns); + + ruler_len_ns = ruler_right_ns - ruler_left_ns; + f64 ruler_len_px = ruler_len_ns / profiler->ns_per_px; + + f64 ruler_cursor_offset_px = (profiler->ruler_start_ns - profiler->view_ns) / profiler->ns_per_px; + f32 ruler_offset_px = (ruler_left_ns - profiler->view_ns) / profiler->ns_per_px; + Vec2 ruler_cursor_pos = VEC2(ruler_cursor_offset_px, 0); + Vec2 ruler_pos = VEC2(ruler_offset_px, 0); + UI_Size ruler_width = UI_Px(ruler_len_px, 1); + + // Vec4 ruler_color = VEC4(0.25, 0.25, 0.25, 0.25); + Vec4 ruler_color = VEC4(0.20, 0.20, 0.20, 0.25); + + // Ruler rect + { + // UI_SetNext(BorderSize, 1); + // UI_SetNext(BorderColor, Color_White); + // UI_PushDF(Opacity, profiler->ruler_opacity) + UI_SetNext(Width, ruler_width); + UI_SetNext(FloatingPos, ruler_pos); + UI_SetNext(BackgroundColor, ruler_color); + UI_SetNext(Flags, UI_BoxFlag_Floating); + UI_BuildBoxEx(UI_KeyF("ruler")); + } + + // Ruler cursor + { + // UI_SetNext(BorderSize, 1); + // UI_SetNext(BorderColor, Color_White); + UI_SetNext(BackgroundColor, timeline_cursor_color); + UI_SetNext(Width, timeline_cursor_width); + UI_SetNext(FloatingPos, ruler_cursor_pos); + UI_SetNext(Anchor, UI_Region_Top); + UI_SetNext(Flags, UI_BoxFlag_Floating); + UI_BuildBoxEx(UI_KeyF("ruler cursor")); + } + } + } + + //- Timeline cursor + { + // Vec2 timeline_cursor_pos = VEC2(old_cursor_offset_px, 0); + Vec2 timeline_cursor_pos = VEC2((profiler->cursor_ns - profiler->view_ns) / profiler->ns_per_px, 0); + + UI_SetNext(Width, timeline_cursor_width); + UI_SetNext(Height, UI_Grow(1, 0)); + UI_SetNext(BackgroundColor, timeline_cursor_color); + UI_SetNext(FloatingPos, timeline_cursor_pos); + UI_SetNext(Flags, UI_BoxFlag_Floating); + UI_BuildBox(); + } + + //- Timeline tooltop + { + b32 show_ruler_time = is_measuring && ruler_len_ns != 0; + b32 show_hovered_zone = hovered_zone != 0; + if (show_ruler_time || show_hovered_zone) + { + UI_Key tooltip_box = UI_KeyF("tooltip"); + Vec2 tooltip_pos = SubVec2(frame->screen_cursor, UI_Anchor(main_box)); + tooltip_pos = AddVec2(tooltip_pos, theme.tooltip_offset_px); + f32 tooltip_opacity = 0.95; + + UI_SetNext(Opacity, tooltip_opacity); + UI_SetNext(BackgroundColor, theme.col.window_bg); + UI_SetNext(BorderColor, theme.col.window_bd); + UI_SetNext(Rounding, UI_Rpx(theme.rounding * 5)); + UI_SetNext(BorderSize, 1); + UI_SetNext(Anchor, UI_Region_TopLeft); + UI_SetNext(FloatingPos, tooltip_pos); + UI_SetNext(Flags, UI_BoxFlag_Floating); + UI_PushDF(Width, UI_Shrink(0, 1)) + UI_PushDF(Height, UI_Shrink(0, 1)) + UI_PushDF(Parent, UI_BuildRowEx(tooltip_box)) + { + UI_BuildSpacer(window_padding, Axis_X); + + UI_SetNext(Width, UI_Shrink(0, 1)); + UI_SetNext(Height, UI_Shrink(0, 1)); + UI_PushDF(Parent, UI_BuildColumn()) + { + UI_BuildSpacer(window_padding, Axis_Y); + + if (show_ruler_time) + { + // UI_SetNext(TextColor, theme.col.positive); + // UI_SetNext(TextColor, theme.col.button_active); + + UI_SetNext(TextColor, theme.col.button_selected); + UI_BuildLabelF("%F", FmtTimeNs(ruler_len_ns, .p = 2)); + + // UI_SetNext(TextColor, theme.col.button_selected); + // UI_BuildLabelF("Ruler"); + + if (show_hovered_zone) + { + UI_BuildSpacer(minor_padding, Axis_Y); + UI_BuildSpacer(minor_padding, Axis_Y); + UI_BuildDivider(UI_Px(1, 0), theme.col.divider, Axis_Y); + UI_BuildSpacer(minor_padding, Axis_Y); + } + } + + if (show_hovered_zone) + { + VisZone *zone = hovered_zone; + UI_PushDF(Parent, UI_BuildRow()) + { + i64 elapsed_ns = zone->end_ns - zone->start_ns; + UI_PushDF(Parent, UI_BuildColumn()) + { + UI_BuildLabelF("%F ", FmtTimeNs(elapsed_ns, .p = 2)); + + if (zone->collapsed_count > 0) + { + UI_PushDF(Parent, UI_BuildRow()) + { + if (zone->collapsed_count == 1) + { + // // UI_BuildLabelF(" (%F)", FmtUint(zone->collapsed_count)); + // UI_SetNext(TextColor, theme.col.hint); + // UI_BuildLabelF("%F", FmtString(zone->name)); + + // UI_SetNext(TextColor, collapsed_text_color); + // UI_BuildLabelF(" (Too small to display)"); + + UI_SetNext(TextColor, collapsed_text_color); + UI_BuildLabelF("1 zone too small to display: "); + UI_SetNext(TextColor, theme.col.hint); + UI_BuildLabelF("%F", FmtString(zone->name)); + } + else + { + // UI_SetNext(TextColor, theme.col.hint); + // UI_BuildLabelF("Zones too small to display"); + + // UI_SetNext(TextColor, collapsed_text_color); + // UI_BuildLabelF(" (%F)", FmtUint(zone->collapsed_count)); + + + // UI_SetNext(TextColor, theme.col.hint); + UI_SetNext(TextColor, collapsed_text_color); + UI_BuildLabelF("%F zones too small to display", FmtUint(zone->collapsed_count)); + } + } + } + else + { + UI_SetNext(TextColor, theme.col.hint); + UI_BuildLabelF("%F", FmtString(zone->name)); + } + } + } + } + + UI_BuildSpacer(window_padding, Axis_Y); + } + UI_BuildSpacer(window_padding, Axis_X); + } + } } } - } - } - // UI_BuildSpacer(window_padding, Axis_Y); + // UI_BuildSpacer(window_padding, Axis_Y); - //- Footer - UI_PushDF(Height, footer_height) - UI_PushDF(ChildAlignment, UI_Region_Center) - UI_PushDF(Parent, UI_BuildRow()) - { - if (IsUnoptimized) - { - UI_PushDF(Width, UI_Shrink(0, 1)) - UI_PushDF(Height, UI_Shrink(0, 1)) - UI_PushDF(ChildAlignment, UI_Region_Left) - UI_PushDF(TextColor, theme.col.warn) + //- Footer + UI_PushDF(Height, footer_height) + UI_PushDF(ChildAlignment, UI_Region_Center) UI_PushDF(Parent, UI_BuildRow()) { - UI_BuildIcon(theme.icon_font, UI_Icon_Warning); - // UI_BuildIcon(theme.icon_font, UI_Icon_Info); - UI_BuildLabelF(" Profiling an unoptimized build"); + if (IsUnoptimized) + { + UI_PushDF(Width, UI_Shrink(0, 1)) + UI_PushDF(Height, UI_Shrink(0, 1)) + UI_PushDF(ChildAlignment, UI_Region_Left) + UI_PushDF(TextColor, theme.col.warn) + UI_PushDF(Parent, UI_BuildRow()) + { + UI_BuildIcon(theme.icon_font, UI_Icon_Warning); + // UI_BuildIcon(theme.icon_font, UI_Icon_Info); + UI_BuildLabelF(" Profiling an unoptimized build"); + } + } + + // Right side text + UI_BuildSpacer(UI_Grow(1, 0), Axis_X); + UI_SetNext(Opacity, UI_Hot(main_box)); + UI_BuildLabelF("%Fs", FmtFloat(SecondsFromNs(profiler->cursor_ns), .p = 3)); } } - - // Right side text - UI_BuildSpacer(UI_Grow(1, 0), Axis_X); - UI_SetNext(Opacity, UI_Hot(main_box)); - UI_BuildLabelF("%Fs", FmtFloat(SecondsFromNs(profiler->cursor_ns), .p = 3)); + UI_BuildSpacer(window_padding, Axis_X); } } - UI_BuildSpacer(window_padding, Axis_X); } } } @@ -4795,7 +4897,6 @@ void V_TickForever(WaveLaneCtx *lane) UI_PushDF(Tag, HashF("vis panels")) { - // FIXME: Remove this ////////////////////////////// @@ -4836,18 +4937,18 @@ void V_TickForever(WaveLaneCtx *lane) } } - //- Test panel + //- Test console panel { V_Panel *parent = subroot_row_panel; V_Panel *panel = PushStruct(perm, V_Panel); panel->parent = parent; panel->axis = Axis_X; DllQueuePush(parent->first, parent->last, panel); - panel->box = UI_KeyF("test raah panel"); + panel->box = UI_KeyF("test raah console panel"); panel->contents_box = UI_KeyF("panel contents box %F", FmtUint(panel->box.v)); panel->resizer_box = UI_KeyF("panel resizer box %F", FmtUint(panel->box.v)); - panel->flags |= V_PanelFlag_Profiler; - panel->pct = 0.25; + panel->flags |= V_PanelFlag_Console; + panel->pct = 0.5; } //- Vis screen panel @@ -4862,6 +4963,21 @@ void V_TickForever(WaveLaneCtx *lane) panel->resizer_box = UI_KeyF("panel resizer box %F", FmtUint(panel->box.v)); panel->pct = 1; } + + + //- Test profiler panel + { + V_Panel *parent = subroot_row_panel; + V_Panel *panel = PushStruct(perm, V_Panel); + panel->parent = parent; + panel->axis = Axis_X; + DllQueuePush(parent->first, parent->last, panel); + panel->box = UI_KeyF("test raah profiler panel"); + panel->contents_box = UI_KeyF("panel contents box %F", FmtUint(panel->box.v)); + panel->resizer_box = UI_KeyF("panel r esizer box %F", FmtUint(panel->box.v)); if (!TweakBool("Hide profiler", 0)) + panel->flags |= V_PanelFlag_Profiler; + panel->pct = 0.5; + } } @@ -4913,14 +5029,14 @@ void V_TickForever(WaveLaneCtx *lane) { SllQueuePop(first_panel_bfs, last_panel_bfs); V_Panel *parent_panel = bfs_parent->panel; - UI_Key parent_contents_box = parent_panel->contents_box; Axis parent_axis = parent_panel->axis; - Rng2 parent_rect = UI_Rect(parent_contents_box); + Rng2 parent_rect = UI_Rect(parent_panel->contents_box); Vec2 parent_dims = DimsFromRng2(parent_rect); - f32 parent_contents_size_px = MaxF32(DimsFromRng2(UI_Rect(parent_contents_box)).v[parent_axis], 1); + f32 parent_contents_size_px = MaxF32(DimsFromRng2(UI_Rect(parent_panel->contents_box)).v[parent_axis], 1); - UI_PushDF(Parent, parent_contents_box) + UI_PushDF(Tag, parent_panel->contents_box.v) + UI_PushDF(Parent, parent_panel->contents_box) { f32 minimum_panel_pct = 0.05; @@ -5006,6 +5122,7 @@ void V_TickForever(WaveLaneCtx *lane) // UI_SetNext(AxisSize, UI_Grow(panel_pct, 1), .axis = parent_axis); UI_SetNext(AxisSize, UI_Grow(panel_pct, 0), .axis = parent_axis); + // UI_SetNext(AxisSize, UI_Px(panel_pct * parent_contents_size_px, 0), .axis = parent_axis); UI_SetNext(AxisSize, UI_Grow(1, 0), .axis = !parent_axis); UI_SetNext(ChildLayoutAxis, parent_axis); UI_PushDF(Tag, panel_box.v) @@ -8131,7 +8248,14 @@ void V_TickForever(WaveLaneCtx *lane) UI_Pop(FontSize); } UI_BuildLabelF(" Arenas: %F", FmtSint(GetGstat(NumArenas))); - UI_BuildLabelF(" Arena memory committed: %F MiB", FmtFloat((f64)GetGstat(ArenaMemoryCommitted) / 1024 / 1024, .p = 3)); + if (PROFILING_ENABLED) + { + UI_BuildLabelF(" Arena memory committed: %F MiB (Collecting profiler samples, so perpetual growth is expected)", FmtFloat((f64)GetGstat(ArenaMemoryCommitted) / 1024 / 1024, .p = 3)); + } + else + { + UI_BuildLabelF(" Arena memory committed: %F MiB", FmtFloat((f64)GetGstat(ArenaMemoryCommitted) / 1024 / 1024, .p = 3)); + } UI_BuildLabelF(" Arena memory reserved: %F TiB", FmtFloat((f64)GetGstat(ArenaMemoryReserved) / 1024 / 1024 / 1024 / 1024, .p = 3)); } UI_BuildSpacer(UI_Px(padding, 1), Axis_Y); diff --git a/src/pp/pp_vis/pp_vis_core.h b/src/pp/pp_vis/pp_vis_core.h index eee405ea..c7ea597a 100644 --- a/src/pp/pp_vis/pp_vis_core.h +++ b/src/pp/pp_vis/pp_vis_core.h @@ -317,6 +317,7 @@ Enum(V_PanelFlag) V_PanelFlag_None = 0, V_PanelFlag_Spawn = (1 << 0), V_PanelFlag_Profiler = (1 << 1), + V_PanelFlag_Console = (1 << 2), }; Struct(V_Panel) diff --git a/src/pp/pp_vis/pp_vis_gpu.g b/src/pp/pp_vis/pp_vis_gpu.g index 9b166b4b..d4875d0f 100644 --- a/src/pp/pp_vis/pp_vis_gpu.g +++ b/src/pp/pp_vis/pp_vis_gpu.g @@ -762,7 +762,8 @@ ComputeShader(V_CompositeCS) Vec4 backdrop_color = 0; { - if (!frame.is_editing || !is_in_world) + // if (!frame.is_editing || !is_in_world) + if (1) { backdrop_color = backdrop.SampleLevel(bilinear_sampler, screen_uv, 0); } diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index 21aa496c..f9cb7021 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -514,6 +514,19 @@ void UI_SetRawTexture(UI_Key key, G_TextureRef tex, Rng2 uv) SllQueuePush(frame->first_cmd_node, frame->last_cmd_node, n); } +void UI_DebugBreak(UI_Key key, UI_DebugBreakFlag debug_break_flags) +{ + UI_Frame *frame = UI_CurrentFrame(); + UI_CmdNode *n = PushStruct(frame->arena, UI_CmdNode); + n->cmd.kind = UI_CmdKind_DebugBreak; + { + n->cmd.debug_break.key = key; + n->cmd.debug_break.flags = debug_break_flags; + } + ++frame->cmds_count; + SllQueuePush(frame->first_cmd_node, frame->last_cmd_node, n); +} + //////////////////////////////////////////////////////////// //~ Begin frame @@ -562,7 +575,7 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) } frame->window_frame = WND_BeginFrame(G_Format_R16G16B16A16_Float, WND_BackbufferSizeMode_MatchMonitor); - ProfZoneDF("Begin UI") + ProfZoneDF("Prepare UI") { UI.cl = G_PrepareCommandList(G_QueueKind_Direct); ResetArena(frame->arena); @@ -617,7 +630,7 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) for (u64 pre_index = UI.boxes_count; pre_index-- > 0;) { UI_Box *box = prev_frame->boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_CheckCursorHover); + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_CheckCursorHover); //- Reset state { box->child_mouse_hovered = 0; @@ -810,7 +823,7 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) UI_Box *box = prev_frame->boxes_pre[pre_index]; UI_Box *parent = box->parent; UI_Feedback *feedback = &box->feedback; - UI_DebugBreak(box, UI_DebugBreakFlag_BuildFeedback); + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_BuildFeedback); // Propagate capture state upwards box->child_mouse_hovered = box->mouse_hovered || box->child_mouse_hovered; @@ -1159,7 +1172,7 @@ GC_Run UI_ScaleRun(Arena *arena, GC_Run unscaled_run, Vec2 scale) void UI_EndFrame(UI_Frame *frame, i32 vsync) { TempArena scratch = BeginScratchNoConflict(); - ProfZoneDF("End UI") + ProfZoneDF("Commit UI") { UI_BoxIter box_iter = Zi; @@ -1174,6 +1187,7 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync) ////////////////////////////// //- Create boxes from build cmds + ProfZoneDF("Create boxes") for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next) { UI_Cmd cmd = cmd_node->cmd; @@ -1217,8 +1231,9 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync) } ////////////////////////////// - //- Update boxes from cmds + //- Apply build cmds + ProfZoneDF("Apply build cmds") for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next) { UI_Cmd cmd = cmd_node->cmd; @@ -1294,9 +1309,31 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync) } } + ////////////////////////////// + //- Apply debug break cmds + + if (UI_IsDebugBreakEnabled) + { + ProfZoneDF("Apply debug break cmds") + for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next) + { + UI_Cmd cmd = cmd_node->cmd; + if (cmd.kind == UI_CmdKind_DebugBreak) + { + UI_Key key = cmd.debug_break.key; + UI_Box *box = UI_BoxFromKey(key); + if (box) + { + box->desc.debug_break_flags |= cmd.debug_break.flags; + } + } + } + } + ////////////////////////////// //- Prune cached boxes + ProfZoneDF("Prune boxes") { u64 prunes_count = 0; UI_Box **prunes = PushStructsNoZero(scratch.arena, UI_Box *, UI.boxes_count); @@ -1355,471 +1392,511 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync) ////////////////////////////// //- Layout - // Prepare layout data u64 boxes_count = UI.boxes_count; UI_Box **boxes_pre = PushStructsNoZero(frame->arena, UI_Box *, boxes_count); UI_Box **boxes_post = PushStructsNoZero(frame->arena, UI_Box *, boxes_count); frame->boxes_pre = boxes_pre; frame->boxes_post = boxes_post; + ProfZoneDF("Layout UI") { - u64 pre_index = 0; - u64 post_index = 0; - for (UI_BoxIterResult ir = UI_FirstBox(scratch.arena, &box_iter, UI_RootKey); ir.box; ir = UI_NextBox(scratch.arena, &box_iter)) + //- Prepare layout data + ProfZoneDF("Layout prepass") { - UI_Box *box = ir.box; - if (ir.pre) + u64 pre_index = 0; + u64 post_index = 0; + for (UI_BoxIterResult ir = UI_FirstBox(scratch.arena, &box_iter, UI_RootKey); ir.box; ir = UI_NextBox(scratch.arena, &box_iter)) { - UI_DebugBreak(box, UI_DebugBreakFlag_PrepLayout); - - box->pre_index = pre_index; - boxes_pre[pre_index] = box; - pre_index += 1; - - // Reset layout data - box->cursor = 0; - box->final_children_size_accum = VEC2(0, 0); - box->solved_dims = VEC2(0, 0); - - // Solve scale & opacity + UI_Box *box = ir.box; + if (ir.pre) { - UI_Box *parent = box->parent; - box->solved_opacity = box->desc.opacity; - box->solved_scale = box->desc.scale; - if (parent) + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_PrepLayout); + + box->pre_index = pre_index; + boxes_pre[pre_index] = box; + pre_index += 1; + + // Reset layout data + box->cursor = 0; + box->final_children_size_accum = VEC2(0, 0); + box->solved_dims = VEC2(0, 0); + + // Solve scale & opacity { - box->solved_opacity = parent->solved_opacity * box->solved_opacity; - box->solved_scale = MulVec2Vec2(parent->solved_scale, box->solved_scale); + UI_Box *parent = box->parent; + box->solved_opacity = box->desc.opacity; + box->solved_scale = box->desc.scale; + if (parent) + { + box->solved_opacity = parent->solved_opacity * box->solved_opacity; + box->solved_scale = MulVec2Vec2(parent->solved_scale, box->solved_scale); + } } } - } - else - { - box->post_index = post_index; - boxes_post[post_index] = box; - post_index += 1; - } - } - Assert(pre_index == boxes_count); - Assert(post_index == boxes_count); - } - - // Solve independent sizes - for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) - { - UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_IndependentSolve); - for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) - { - UI_Size size = box->desc.pref_semantic_dims[axis]; - if (size.kind == UI_SizeKind_Pixel) - { - box->solved_dims.v[axis] = size.v; - } - else if (size.kind == UI_SizeKind_Shrink) - { - if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText)) + else { - f32 text_size = 0; - if (axis == Axis_X) + box->post_index = post_index; + boxes_post[post_index] = box; + post_index += 1; + } + } + Assert(pre_index == boxes_count); + Assert(post_index == boxes_count); + } + + //- Solve independent sizes + ProfZoneDF("Solve independent sizes") + for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) + { + UI_Box *box = boxes_pre[pre_index]; + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_IndependentSolve); + for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) + { + UI_Size size = box->desc.pref_semantic_dims[axis]; + if (size.kind == UI_SizeKind_Pixel) + { + if (TweakBool("RAAAAAAAAAAAAAH", 1)) { - text_size = box->glyph_run.baseline_length; + box->solved_dims.v[axis] = size.v; } else { - text_size = box->glyph_run.font_ascent + box->glyph_run.font_descent; - } - box->solved_dims.v[axis] = text_size + (size.v * 2); - } - else if (!SPR_IsSheetKeyNil(box->desc.sprite_sheet)) - { - box->solved_dims.v[axis] = box->sprite.tex_rect.p1.v[axis] - box->sprite.tex_rect.p0.v[axis] + (size.v * 2); - } - } - } - } - - // Solve upwards-dependent sizes along layout axis - for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) - { - UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_UpwardsDependentSolveLayoutAxis); - if (box->parent) - { - Axis axis = box->parent->desc.child_layout_axis; - UI_Size size = box->desc.pref_semantic_dims[axis]; - if (size.kind == UI_SizeKind_Grow) - { - f32 match_size = 0; - b32 found_match = 0; - for (UI_Box *ancestor = box->parent; ancestor != 0 && !found_match; ancestor = ancestor->parent) - { - UI_Size ancestor_size = ancestor->desc.pref_semantic_dims[axis]; - if ( - ancestor_size.kind == UI_SizeKind_Pixel || ( - ancestor_size.kind == UI_SizeKind_Shrink && ( - AnyBit(box->desc.flags, UI_BoxFlag_DrawText) || - !SPR_IsSheetKeyNil(box->desc.sprite_sheet) - ) - ) - ) - { - // Match independent ancestor - match_size = ancestor->solved_dims.v[axis]; - found_match = 1; + box->solved_dims.v[axis] = CeilF32(size.v); } } - box->solved_dims.v[axis] = match_size * size.v; - } - } - } - - // Solve downwards-dependent sizes - for (u64 post_index = 0; post_index < boxes_count; ++post_index) - { - UI_Box *box = boxes_post[post_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_DownwardsDependentSolve); - for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) - { - UI_Size size = box->desc.pref_semantic_dims[axis]; - if (size.kind == UI_SizeKind_Shrink && !(AnyBit(box->desc.flags, UI_BoxFlag_DrawText) || !SPR_IsSheetKeyNil(box->desc.sprite_sheet))) - { - f32 accum = 0; - for (UI_Box *child = box->first; child; child = child->next) + else if (size.kind == UI_SizeKind_Shrink) { - if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating)) + if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText)) { - f32 child_size = child->solved_dims.v[axis]; - if (axis == box->desc.child_layout_axis) + f32 text_size = 0; + if (axis == Axis_X) { - accum += child_size; + text_size = box->glyph_run.baseline_length; } else { - accum = MaxF32(child_size, accum); + text_size = box->glyph_run.font_ascent + box->glyph_run.font_descent; + } + if (TweakBool("RAAAAAAAAAAAAAH", 1)) + { + box->solved_dims.v[axis] = text_size + (size.v * 2); + } + else + { + box->solved_dims.v[axis] = CeilF32(text_size + (size.v * 2)); + } + } + else if (!SPR_IsSheetKeyNil(box->desc.sprite_sheet)) + { + if (TweakBool("RAAAAAAAAAAAAAH", 1)) + { + box->solved_dims.v[axis] = box->sprite.tex_rect.p1.v[axis] - box->sprite.tex_rect.p0.v[axis] + (size.v * 2); + } + else + { + box->solved_dims.v[axis] = CeilF32(box->sprite.tex_rect.p1.v[axis] - box->sprite.tex_rect.p0.v[axis] + (size.v * 2)); } } } - box->solved_dims.v[axis] = CeilF32(accum + (size.v * 2)); } } - } - // Solve upwards-dependent sizes along non-layout axis - for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) - { - UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_UpwardsDependentSolveNonLayoutAxis); - if (box->parent) + //- Solve upwards-dependent sizes along layout axis + ProfZoneDF("Solve upwards-dependent sizes (along layout-axis)") + for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { - Axis axis = !box->parent->desc.child_layout_axis; - UI_Size size = box->desc.pref_semantic_dims[axis]; - if (size.kind == UI_SizeKind_Grow) + UI_Box *box = boxes_pre[pre_index]; + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_UpwardsDependentSolveLayoutAxis); + if (box->parent) { - box->solved_dims.v[axis] = box->parent->solved_dims.v[axis] * size.v; - } - } - } - - // Solve violations - for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) - { - UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_SolveViolations); - for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) - { - f32 box_size = box->solved_dims.v[axis]; - { - // Accumulate non-floating sizes - f32 unconstrained_size_accum = 0; - f32 flex_accum = 0; - f32 violation = 0; + Axis axis = box->parent->desc.child_layout_axis; + UI_Size size = box->desc.pref_semantic_dims[axis]; + if (size.kind == UI_SizeKind_Grow) { + f32 match_size = 0; + b32 found_match = 0; + for (UI_Box *ancestor = box->parent; ancestor != 0 && !found_match; ancestor = ancestor->parent) + { + UI_Size ancestor_size = ancestor->desc.pref_semantic_dims[axis]; + if ( + ancestor_size.kind == UI_SizeKind_Pixel || + ( + ancestor_size.kind == UI_SizeKind_Shrink && + ( + AnyBit(box->desc.flags, UI_BoxFlag_DrawText) || + !SPR_IsSheetKeyNil(box->desc.sprite_sheet) + ) + ) + ) + { + // Match independent ancestor + match_size = ancestor->solved_dims.v[axis]; + found_match = 1; + } + } + box->solved_dims.v[axis] = match_size * size.v; + } + } + } + + //- Solve downwards-dependent sizes + ProfZoneDF("Solve downwards-dependent sizes") + for (u64 post_index = 0; post_index < boxes_count; ++post_index) + { + UI_Box *box = boxes_post[post_index]; + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_DownwardsDependentSolve); + for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) + { + UI_Size size = box->desc.pref_semantic_dims[axis]; + if (size.kind == UI_SizeKind_Shrink && !(AnyBit(box->desc.flags, UI_BoxFlag_DrawText) || !SPR_IsSheetKeyNil(box->desc.sprite_sheet))) + { + f32 accum = 0; for (UI_Box *child = box->first; child; child = child->next) { - b32 is_floating = AnyBit(child->desc.flags, UI_BoxFlag_Floating); - if (!is_floating) + if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating)) { - f32 size = child->solved_dims.v[axis]; - f32 strictness = child->desc.pref_semantic_dims[axis].strictness; - f32 flex = size * (1.0 - strictness); + f32 child_size = child->solved_dims.v[axis]; if (axis == box->desc.child_layout_axis) { - unconstrained_size_accum += size; - flex_accum += flex; + accum += child_size; } else { - unconstrained_size_accum = MaxF32(unconstrained_size_accum, size); - flex_accum = MaxF32(flex_accum, flex); + accum = MaxF32(child_size, accum); } } } - unconstrained_size_accum = FloorF32(unconstrained_size_accum); - violation = unconstrained_size_accum - box_size; + // box->solved_dims.v[axis] = CeilF32(accum + (size.v * 2)); + box->solved_dims.v[axis] = accum + (size.v * 2); } + } + } + + //- Solve upwards-dependent sizes along non-layout axis + ProfZoneDF("Solve upwards-dependent sizes (along non-layout-axis)") + for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) + { + UI_Box *box = boxes_pre[pre_index]; + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_UpwardsDependentSolveNonLayoutAxis); + if (box->parent) + { + Axis axis = !box->parent->desc.child_layout_axis; + UI_Size size = box->desc.pref_semantic_dims[axis]; + if (size.kind == UI_SizeKind_Grow) { - f32 size_accum = 0; - for (UI_Box *child = box->first; child; child = child->next) - { - b32 is_floating = AnyBit(child->desc.flags, UI_BoxFlag_Floating); - f32 unconstrained_size = child->solved_dims.v[axis]; - f32 strictness = child->desc.pref_semantic_dims[axis].strictness; - f32 flex = unconstrained_size * (1.0 - strictness); - f32 new_size = unconstrained_size; - // Solve non-floating violation - if (!is_floating && violation > 0 && flex_accum > 0) - { - if (axis == box->desc.child_layout_axis) - { - f32 chopoff = MinF32(flex, violation * (flex / flex_accum)); - new_size = new_size - chopoff; - } - else if (new_size > box_size) - { - new_size = MaxF32(new_size - flex, box_size); - } - } - // Solve floating violation - if (is_floating && new_size > box_size) - { - b32 should_clamp = ( - !(axis == Axis_X && AnyBit(child->desc.flags, UI_BoxFlag_DontClampFloatingX)) && - !(axis == Axis_Y && AnyBit(child->desc.flags, UI_BoxFlag_DontClampFloatingY)) - ); - if (should_clamp) - { - new_size = MaxF32(new_size - flex, box_size); - } - } - if (!is_floating) - { - size_accum += new_size; - } - child->solved_dims.v[axis] = new_size; - } - box->final_children_size_accum.v[axis] = size_accum; + box->solved_dims.v[axis] = box->parent->solved_dims.v[axis] * size.v; } } } - } - // Solve final positions - for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) - { - UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_FinalSolve); - - UI_Box *parent = box->parent; - - // TODO: Distinguish between baseline alignment & visual alignment - UI_RegionPair child_alignment = UI_PairFromRegion(box->desc.child_alignment); - UI_RegionPair alignment_in_parent = UI_PairFromRegion(parent ? parent->desc.child_alignment : UI_Region_TopLeft); - - Axis child_layout_axis = box->desc.child_layout_axis; - Axis layout_axis_in_parent = parent ? parent->desc.child_layout_axis : Axis_X; - - b32 is_floating = AnyBit(box->desc.flags, UI_BoxFlag_Floating); - - // Apply scale - for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) + //- Solve violations + ProfZoneDF("Solve violations") + for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { - f32 unscaled_size = box->solved_dims.v[axis]; - f32 scaled_size = 0; - if (box->solved_scale.v[axis] == 1) - { - scaled_size = RoundF32(unscaled_size); - // scaled_size = unscaled_size; - } - else - { - scaled_size = unscaled_size * box->solved_scale.v[axis]; - } - box->solved_dims.v[axis] = scaled_size; - } - - // Compute anchor offset - Vec2 anchor_offset = Zi; - { - UI_RegionPair anchor_region = UI_PairFromRegion(box->desc.anchor); + UI_Box *box = boxes_pre[pre_index]; + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_SolveViolations); for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) { - UI_AxisRegion anchor = anchor_region.v[axis]; - switch (anchor) + f32 box_size = box->solved_dims.v[axis]; + { + // Accumulate non-floating sizes + f32 unconstrained_size_accum = 0; + f32 flex_accum = 0; + f32 violation = 0; + { + for (UI_Box *child = box->first; child; child = child->next) + { + b32 is_floating = AnyBit(child->desc.flags, UI_BoxFlag_Floating); + if (!is_floating) + { + f32 size = RoundF32(child->solved_dims.v[axis]); + f32 strictness = child->desc.pref_semantic_dims[axis].strictness; + f32 flex = size * (1.0 - strictness); + if (axis == box->desc.child_layout_axis) + { + unconstrained_size_accum += size; + flex_accum += flex; + } + else + { + unconstrained_size_accum = MaxF32(unconstrained_size_accum, size); + flex_accum = MaxF32(flex_accum, flex); + } + } + } + unconstrained_size_accum = FloorF32(unconstrained_size_accum); + violation = unconstrained_size_accum - box_size; + } + { + f32 size_accum = 0; + for (UI_Box *child = box->first; child; child = child->next) + { + b32 is_floating = AnyBit(child->desc.flags, UI_BoxFlag_Floating); + f32 unconstrained_size = RoundF32(child->solved_dims.v[axis]); + f32 strictness = child->desc.pref_semantic_dims[axis].strictness; + f32 flex = unconstrained_size * (1.0 - strictness); + f32 new_size = unconstrained_size; + // Solve non-floating violation + if (!is_floating && violation > 0 && flex_accum > 0) + { + if (axis == box->desc.child_layout_axis) + { + f32 chopoff = MinF32(flex, violation * (flex / flex_accum)); + new_size = new_size - chopoff; + } + else if (new_size > box_size) + { + new_size = MaxF32(new_size - flex, box_size); + } + } + // Solve floating violation + if (is_floating && new_size > box_size) + { + b32 should_clamp = ( + !(axis == Axis_X && AnyBit(child->desc.flags, UI_BoxFlag_DontClampFloatingX)) && + !(axis == Axis_Y && AnyBit(child->desc.flags, UI_BoxFlag_DontClampFloatingY)) + ); + if (should_clamp) + { + new_size = MaxF32(new_size - flex, box_size); + } + } + if (!is_floating) + { + size_accum += RoundF32(new_size); + } + child->solved_dims.v[axis] = new_size; + } + box->final_children_size_accum.v[axis] = size_accum; + } + } + } + } + + //- Solve final positions + ProfZoneDF("Solve final screen positions") + for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) + { + UI_Box *box = boxes_pre[pre_index]; + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_FinalSolve); + + UI_Box *parent = box->parent; + + // TODO: Distinguish between baseline alignment & visual alignment + UI_RegionPair child_alignment = UI_PairFromRegion(box->desc.child_alignment); + UI_RegionPair alignment_in_parent = UI_PairFromRegion(parent ? parent->desc.child_alignment : UI_Region_TopLeft); + + Axis child_layout_axis = box->desc.child_layout_axis; + Axis layout_axis_in_parent = parent ? parent->desc.child_layout_axis : Axis_X; + + b32 is_floating = AnyBit(box->desc.flags, UI_BoxFlag_Floating); + + //- Apply scale + for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) + { + f32 unscaled_size = box->solved_dims.v[axis]; + f32 scaled_size = 0; + if (box->solved_scale.v[axis] == 1) + { + scaled_size = RoundF32(unscaled_size); // Can cause layout issues + // scaled_size = unscaled_size; + } + else + { + scaled_size = unscaled_size * box->solved_scale.v[axis]; + } + box->solved_dims.v[axis] = scaled_size; + } + + //- Compute anchor offset + Vec2 anchor_offset = Zi; + { + UI_RegionPair anchor_region = UI_PairFromRegion(box->desc.anchor); + for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) + { + UI_AxisRegion anchor = anchor_region.v[axis]; + switch (anchor) + { + default: break; + case UI_AxisRegion_Center: + { + anchor_offset.v[axis] = box->solved_dims.v[axis] * 0.5; + } break; + case UI_AxisRegion_End: + { + anchor_offset.v[axis] = box->solved_dims.v[axis]; + } break; + } + } + } + + // Initialize layout cursor based on alignment + { + Axis axis = child_layout_axis; + UI_AxisRegion alignment = child_alignment.v[axis]; + f32 box_size = box->solved_dims.v[axis]; + f32 size_accum = box->final_children_size_accum.v[axis]; + switch(alignment) { default: break; case UI_AxisRegion_Center: { - anchor_offset.v[axis] = box->solved_dims.v[axis] * 0.5; + box->cursor = box_size / 2 - size_accum / 2; } break; case UI_AxisRegion_End: { - anchor_offset.v[axis] = box->solved_dims.v[axis]; + box->cursor = box_size - size_accum; } break; } + // box->cursor = FloorF32(box->cursor); } - } - // Initialize layout cursor based on alignment - { - Axis axis = child_layout_axis; - UI_AxisRegion alignment = child_alignment.v[axis]; - f32 box_size = box->solved_dims.v[axis]; - f32 size_accum = box->final_children_size_accum.v[axis]; - switch(alignment) + //- Solve screen rect { - default: break; - case UI_AxisRegion_Center: + // Compute offset + Vec2 offset = Zi; { - box->cursor = box_size / 2 - size_accum / 2; - } break; - case UI_AxisRegion_End: - { - box->cursor = box_size - size_accum; - } break; - } - // box->cursor = FloorF32(box->cursor); - } - - // Solve screen rect - { - // Compute offset - Vec2 offset = Zi; - { - // Floating box offset - if (is_floating) - { - offset = box->desc.floating_pos; - offset = SubVec2(offset, anchor_offset); - } - // Non-floating box offset - else if (parent) - { - // Compute offset in layout direction (based on parent cursor) - offset.v[layout_axis_in_parent] = parent->cursor; - // Compute offset in non-layout direction (based on alignment) + // Floating box offset + if (is_floating) { - Axis axis = !layout_axis_in_parent; - UI_AxisRegion alignment = alignment_in_parent.v[axis]; - switch(alignment) + offset = box->desc.floating_pos; + offset = SubVec2(offset, anchor_offset); + } + // Non-floating box offset + else if (parent) + { + // Compute offset in layout direction (based on parent cursor) + offset.v[layout_axis_in_parent] = parent->cursor; + // Compute offset in non-layout direction (based on alignment) { - default: break; - case UI_AxisRegion_Center: + Axis axis = !layout_axis_in_parent; + UI_AxisRegion alignment = alignment_in_parent.v[axis]; + switch(alignment) { - f32 parent_size = parent->solved_dims.v[axis]; - f32 box_size = box->solved_dims.v[axis]; - offset.v[axis] = parent_size / 2 - box_size / 2; - } break; - case UI_AxisRegion_End: - { - f32 parent_size = parent->solved_dims.v[axis]; - f32 box_size = box->solved_dims.v[axis]; - offset.v[axis] = parent_size - box_size; - } break; + default: break; + case UI_AxisRegion_Center: + { + f32 parent_size = parent->solved_dims.v[axis]; + f32 box_size = box->solved_dims.v[axis]; + offset.v[axis] = parent_size / 2 - box_size / 2; + } break; + case UI_AxisRegion_End: + { + f32 parent_size = parent->solved_dims.v[axis]; + f32 box_size = box->solved_dims.v[axis]; + offset.v[axis] = parent_size - box_size; + } break; + } } } } + + //- Compute rect + Vec2 screen_pos = parent ? AddVec2(parent->screen_rect.p0, offset) : VEC2(0, 0); + if (is_floating) + { + Vec2 overshoot = Zi; + if (!AnyBit(box->desc.flags, UI_BoxFlag_DontClampFloatingX)) + { + overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x); + screen_pos.x = MaxF32(parent->screen_rect.p0.x, screen_pos.x - overshoot.x); + } + if (!AnyBit(box->desc.flags, UI_BoxFlag_DontClampFloatingY)) + { + overshoot.y = MaxF32((screen_pos.y + box->solved_dims.y) - parent->screen_rect.p1.y, 0); + screen_pos.y = MaxF32(parent->screen_rect.p0.y, screen_pos.y - overshoot.y); + } + } + + box->screen_rect.p0 = screen_pos; + box->screen_rect.p1 = AddVec2(box->screen_rect.p0, box->solved_dims); + if (box->solved_scale.x == 1) { - offset.x = RoundF32(offset.x); + box->screen_rect.p0.x = RoundF32(box->screen_rect.p0.x); + box->screen_rect.p1.x = RoundF32(box->screen_rect.p1.x); } if (box->solved_scale.y == 1) { - offset.y = RoundF32(offset.y); + box->screen_rect.p0.y = RoundF32(box->screen_rect.p0.y); + box->screen_rect.p1.y = RoundF32(box->screen_rect.p1.y); + } + + box->screen_anchor = AddVec2(box->screen_rect.p0, anchor_offset); + + // Update parent cursor + if (parent && !is_floating) + { + parent->cursor += box->solved_dims.v[layout_axis_in_parent]; } } - // Compute rect - Vec2 screen_pos = parent ? AddVec2(parent->screen_rect.p0, offset) : VEC2(0, 0); - if (is_floating) + //- Solve scissor { - Vec2 overshoot = Zi; - if (!AnyBit(box->desc.flags, UI_BoxFlag_DontClampFloatingX)) + box->solved_scissor = Rng2Inf; + if (box->desc.flags & UI_BoxFlag_Scissor || !parent) { - overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x); - screen_pos.x = MaxF32(parent->screen_rect.p0.x, screen_pos.x - overshoot.x); + box->solved_scissor = box->screen_rect; } - if (!AnyBit(box->desc.flags, UI_BoxFlag_DontClampFloatingY)) + if (parent) { - overshoot.y = MaxF32((screen_pos.y + box->solved_dims.y) - parent->screen_rect.p1.y, 0); - screen_pos.y = MaxF32(parent->screen_rect.p0.y, screen_pos.y - overshoot.y); + box->solved_scissor = IntersectRng2(box->solved_scissor, parent->solved_scissor); } } - box->screen_rect.p0 = screen_pos; - box->screen_rect.p1 = AddVec2(box->screen_rect.p0, box->solved_dims); - box->screen_anchor = AddVec2(box->screen_rect.p0, anchor_offset); - // Update parent cursor - if (parent && !is_floating) + //- Solve screen rounding { - parent->cursor += box->solved_dims.v[layout_axis_in_parent]; - } - } + UI_Round rounding = box->desc.rounding; + Vec2 half_dims = MulVec2(SubVec2(box->screen_rect.p1, box->screen_rect.p0), 0.5); + f32 min_half_dims = MinF32(half_dims.x, half_dims.y); + f32 final_rounding_tl = 0; + f32 final_rounding_tr = 0; + f32 final_rounding_br = 0; + f32 final_rounding_bl = 0; - // Solve scissor - { - box->solved_scissor = Rng2Inf; - if (box->desc.flags & UI_BoxFlag_Scissor || !parent) - { - box->solved_scissor = box->screen_rect; - } - if (parent) - { - box->solved_scissor = IntersectRng2(box->solved_scissor, parent->solved_scissor); - } - } - - // Solve screen rounding - { - UI_Round rounding = box->desc.rounding; - Vec2 half_dims = MulVec2(SubVec2(box->screen_rect.p1, box->screen_rect.p0), 0.5); - f32 min_half_dims = MinF32(half_dims.x, half_dims.y); - f32 final_rounding_tl = 0; - f32 final_rounding_tr = 0; - f32 final_rounding_br = 0; - f32 final_rounding_bl = 0; - - switch(rounding.kind) - { - default: break; - case UI_RoundKind_Pixel: + switch(rounding.kind) { - final_rounding_tl = rounding.v; - final_rounding_tr = final_rounding_tl; - final_rounding_br = final_rounding_tl; - final_rounding_bl = final_rounding_tl; - } break; - case UI_RoundKind_Grow: + default: break; + case UI_RoundKind_Pixel: + { + final_rounding_tl = rounding.v; + final_rounding_tr = final_rounding_tl; + final_rounding_br = final_rounding_tl; + final_rounding_bl = final_rounding_tl; + } break; + case UI_RoundKind_Grow: + { + final_rounding_tl = rounding.v * min_half_dims; + final_rounding_tr = final_rounding_tl; + final_rounding_br = final_rounding_tl; + final_rounding_bl = final_rounding_tl; + } break; + } + + // Clamp rounding based on parent rounding + // if (parent && !AllBits(box->desc.flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloating)) + if (parent) { - final_rounding_tl = rounding.v * min_half_dims; - final_rounding_tr = final_rounding_tl; - final_rounding_br = final_rounding_tl; - final_rounding_bl = final_rounding_tl; - } break; + Vec2 vtl = SubVec2(VEC2(parent->screen_rect.p0.x, parent->screen_rect.p0.y), VEC2(box->screen_rect.p0.x, box->screen_rect.p0.y)); + Vec2 vtr = SubVec2(VEC2(parent->screen_rect.p1.x, parent->screen_rect.p0.y), VEC2(box->screen_rect.p1.x, box->screen_rect.p0.y)); + Vec2 vbr = SubVec2(VEC2(parent->screen_rect.p1.x, parent->screen_rect.p1.y), VEC2(box->screen_rect.p1.x, box->screen_rect.p1.y)); + Vec2 vbl = SubVec2(VEC2(parent->screen_rect.p0.x, parent->screen_rect.p1.y), VEC2(box->screen_rect.p0.x, box->screen_rect.p1.y)); + final_rounding_tl = MaxF32(final_rounding_tl, parent->rounding_tl - Vec2Len(vtl)); + final_rounding_tr = MaxF32(final_rounding_tr, parent->rounding_tr - Vec2Len(vtr)); + final_rounding_br = MaxF32(final_rounding_br, parent->rounding_br - Vec2Len(vbr)); + final_rounding_bl = MaxF32(final_rounding_bl, parent->rounding_bl - Vec2Len(vbl)); + } + + box->rounding_tl = final_rounding_tl; + box->rounding_tr = final_rounding_tr; + box->rounding_br = final_rounding_br; + box->rounding_bl = final_rounding_bl; } - // Clamp rounding based on parent rounding - // if (parent && !AllBits(box->desc.flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloating)) - if (parent) - { - Vec2 vtl = SubVec2(VEC2(parent->screen_rect.p0.x, parent->screen_rect.p0.y), VEC2(box->screen_rect.p0.x, box->screen_rect.p0.y)); - Vec2 vtr = SubVec2(VEC2(parent->screen_rect.p1.x, parent->screen_rect.p0.y), VEC2(box->screen_rect.p1.x, box->screen_rect.p0.y)); - Vec2 vbr = SubVec2(VEC2(parent->screen_rect.p1.x, parent->screen_rect.p1.y), VEC2(box->screen_rect.p1.x, box->screen_rect.p1.y)); - Vec2 vbl = SubVec2(VEC2(parent->screen_rect.p0.x, parent->screen_rect.p1.y), VEC2(box->screen_rect.p0.x, box->screen_rect.p1.y)); - final_rounding_tl = MaxF32(final_rounding_tl, parent->rounding_tl - Vec2Len(vtl)); - final_rounding_tr = MaxF32(final_rounding_tr, parent->rounding_tr - Vec2Len(vtr)); - final_rounding_br = MaxF32(final_rounding_br, parent->rounding_br - Vec2Len(vbr)); - final_rounding_bl = MaxF32(final_rounding_bl, parent->rounding_bl - Vec2Len(vbl)); - } - - box->rounding_tl = final_rounding_tl; - box->rounding_tr = final_rounding_tr; - box->rounding_br = final_rounding_br; - box->rounding_bl = final_rounding_bl; + box->gen = box->old_gen; } - - box->gen = box->old_gen; } + ////////////////////////////// //- Render @@ -1830,11 +1907,11 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync) //- Build render data // Build GPU rect data - ProfZoneDF("Build UI GPU rects") + ProfZoneDF("Build GPU rects") for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_BuildGpuData); + UI_ExecuteDebugBreak(box, UI_DebugBreakFlag_BuildGpuData); GC_Run raw_run_unscaled = box->glyph_run; GC_Run raw_run = UI_ScaleRun(frame->arena, raw_run_unscaled, box->solved_scale); @@ -1879,6 +1956,7 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync) UI_AxisRegion y_alignment = child_alignment.v[Axis_Y]; // Box rect + ProfZoneDF("GPU box rect") { UI_GpuRect *rect = PushStruct(frame->rects_arena, UI_GpuRect); rect->bounds = box->screen_rect; @@ -1909,7 +1987,9 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync) } } + // Text rects if (should_upload_text || AnyBit(frame->frame_flags, UI_FrameFlag_Debug)) + ProfZoneDF("GPU text rects") { f32 max_baseline_length = CeilF32(DimsFromRng2(box->screen_rect).x); b32 should_truncate = FloorF32(raw_run.baseline_length) > max_baseline_length && !AnyBit(box->desc.flags, UI_BoxFlag_DontTruncateText); @@ -2037,7 +2117,7 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync) ////////////////////////////// //- Upload data to GPU - ProfZoneDF("UI GPU upload") + ProfZoneDF("Upload boxes to GPU") G_ProfZoneDF(UI.cl, "UI GPU upload") { // Target diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index f9691c7e..fea88c1b 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -262,6 +262,7 @@ Enum(UI_CmdKind) { UI_CmdKind_None, UI_CmdKind_BuildBox, + UI_CmdKind_DebugBreak, UI_CmdKind_SetRawTexture, }; @@ -312,6 +313,11 @@ Struct(UI_Cmd) { UI_BoxDesc box; struct + { + UI_Key key; + UI_DebugBreakFlag flags; + } debug_break; + struct { UI_Key key; G_TextureRef tex; @@ -568,12 +574,16 @@ UI_Checkpoint UI_TopCp(void); UI_Key UI_BuildBoxEx(UI_Key semantic_key); #define UI_BuildBox() UI_BuildBoxEx(UI_NilKey) +void UI_DebugBreak(UI_Key key, UI_DebugBreakFlag debug_break_flags); + void UI_SetRawTexture(UI_Key key, G_TextureRef tex, Rng2 uv); -#if IsRtcEnabled - #define UI_DebugBreak(box, target_flags) do { if (box->desc.debug_break_flags & target_flags) { DEBUGBREAK; } } while (0) +#define UI_IsDebugBreakEnabled IsRtcEnabled + +#if UI_IsDebugBreakEnabled + #define UI_ExecuteDebugBreak(box, target_flags) do { if ((box)->desc.debug_break_flags & (target_flags)) { DEBUGBREAK; } } while (0) #else - #define UI_DebugBreak(...) + #define UI_ExecuteDebugBreak(...) #endif ////////////////////////////////////////////////////////////