world blend wip

This commit is contained in:
jacob 2026-02-25 15:58:46 -06:00
parent 5ca777f646
commit 55311f7b98
6 changed files with 156 additions and 50 deletions

View File

@ -38,7 +38,7 @@ i64 SaturateI64(i64 v) { return v < 0 ? 0 : v > 1 ? 1 : v; }
f64 SaturateF64(f64 v) { return v < 0 ? 0 : v > 1 ? 1 : v; } f64 SaturateF64(f64 v) { return v < 0 ? 0 : v > 1 ? 1 : v; }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Float ops //~ Numeric ops
//- Sign //- Sign

View File

@ -234,7 +234,7 @@ i64 SaturateI64(i64 v);
f64 SaturateF64(f64 v); f64 SaturateF64(f64 v);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Float ops //~ Numeric ops
//- Round //- Round
#define RoundF32 roundf #define RoundF32 roundf
@ -257,6 +257,8 @@ f64 SaturateF64(f64 v);
#define ModF64 fmod #define ModF64 fmod
//- Abs //- Abs
#define AbsI32 abs
#define AbsI64 llabs
#define AbsF32 fabsf #define AbsF32 fabsf
#define AbsF64 fabs #define AbsF64 fabs

View File

@ -89,39 +89,39 @@ u32 countof(T arr[N])
#define ClampF64 (f64)clamp #define ClampF64 (f64)clamp
//- Round //- Round
#define RoundF32 (f32)round #define RoundF32(a) round((f32)(a))
#define RoundF64 (f64)round #define RoundF64(a) round((f64)(a))
//- Floor //- Floor
#define FloorF32 (f32)floor #define FloorF32(a) floor((f32)(a))
#define FloorF64 (f64)floor #define FloorF64(a) floor((f64)(a))
//- Ceil //- Ceil
#define CeilF32 (f32)ceil #define CeilF32(a) ceil((f32)(a))
#define CeilF64 (f64)ceil #define CeilF64(a) ceil((f64)(a))
//- Trunc //- Trunc
#define TruncF32 (f32)trunc #define TruncF32(a) trunc((f32)(a))
#define TruncF64 (f64)trunc #define TruncF64(a) trunc((f64)(a))
//- Mod //- Mod
#define ModF32 (f32)fmod #define ModF32(a, b) fmod((f32)(a), (f32)(b))
#define ModF64 (f64)fmod #define ModF64(a, b) fmod((f64)(a), (f64)(b))
//- Abs //- Abs
#define AbsF32 (f32)abs #define AbsF32(a) abs((f32)(a))
#define AbsF64 (f64)abs #define AbsF64(a) abs((f64)(a))
//- Sign //- Sign
#define SignF32 (f32)sign #define SignF32(a) sign((f32)(a))
#define SignF64 (f64)sign #define SignF64(a) sign((f64)(a))
//- Smoothstep //- Smoothstep
#define SmoothstepF32 (f32)smoothstep #define SmoothstepF32(a, b, t) smoothstep((f32)(a), (f32)(b), (f32)(t))
#define SmoothstepF64 (f64)smoothstep #define SmoothstepF64(a, b, t) smoothstep((f64)(a), (f64)(b), (f64)(t))
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Float ops //~ Numeric ops
//- Normalize //- Normalize

View File

@ -1,20 +1,14 @@
// Project-wide configurable constants // Project-wide configurable constants
// How many ticks back in time should the user thread blend between?
// <Delay> = <USER_INTERP_RATIO> * <Tick interval>
// E.g: At 1.5, the user thread will render 75ms back in time if the sim runs at 50tps
#define USER_INTERP_RATIO 1.2
#define USER_INTERP_ENABLED 1
#define SIM_MAX_PING 5.0 #define SIM_MAX_PING 5.0
#define SIM_PHYSICS_SUBSTEPS 4 #define SIM_PHYSICS_SUBSTEPS 4
#define SIM_TICKS_PER_SECOND 64 #define SIM_TICKS_PER_SECOND 32
#define SIM_TICK_INTERVAL_NS (NsFromSeconds(1) / SIM_TICKS_PER_SECOND) #define SIM_TICK_INTERVAL_NS (NsFromSeconds(1) / SIM_TICKS_PER_SECOND)
// Like USER_INTERP_RATIO, but applies to snapshots received by the local sim from the // Like USER_INTERP_RATIO, but applies to snapshots received by the local sim from the
// master sim (how far back in time should the client render the server's state) // master sim (how far back in time should the client render the server's state)
#define SIM_CLIENT_INTERP_RATIO 2.0 // #define SIM_CLIENT_INTERP_RATIO 2.0
#define GPU_NAMES IsRtcEnabled #define GPU_NAMES IsRtcEnabled

View File

@ -363,7 +363,6 @@ void V_TickForever(WaveLaneCtx *lane)
P_World *sim_world = P_AcquireWorld(); P_World *sim_world = P_AcquireWorld();
P_World *predict_world = P_AcquireWorld(); P_World *predict_world = P_AcquireWorld();
P_World *local_world = P_AcquireWorld();
i64 local_controls_cap = NextPow2U64(SIM_MAX_PING * SIM_TICKS_PER_SECOND); i64 local_controls_cap = NextPow2U64(SIM_MAX_PING * SIM_TICKS_PER_SECOND);
P_Control *local_controls = PushStructs(perm, P_Control, local_controls_cap); P_Control *local_controls = PushStructs(perm, P_Control, local_controls_cap);
@ -524,6 +523,7 @@ void V_TickForever(WaveLaneCtx *lane)
frame->quads_arena = AcquireArena(Gibi(64)); frame->quads_arena = AcquireArena(Gibi(64));
frame->dverts_arena = AcquireArena(Gibi(64)); frame->dverts_arena = AcquireArena(Gibi(64));
frame->dvert_idxs_arena = AcquireArena(Gibi(64)); frame->dvert_idxs_arena = AcquireArena(Gibi(64));
frame->local_world = P_AcquireWorld();
} }
////////////////////////////// //////////////////////////////
@ -577,11 +577,13 @@ void V_TickForever(WaveLaneCtx *lane)
Arena *old_quads_arena = frame->quads_arena; Arena *old_quads_arena = frame->quads_arena;
Arena *old_dverts_arena = frame->dverts_arena; Arena *old_dverts_arena = frame->dverts_arena;
Arena *old_dvert_idxs_arena = frame->dvert_idxs_arena; Arena *old_dvert_idxs_arena = frame->dvert_idxs_arena;
P_World *old_local_world = frame->local_world;
ZeroStruct(frame); ZeroStruct(frame);
frame->arena = old_arena; frame->arena = old_arena;
frame->quads_arena = old_quads_arena; frame->quads_arena = old_quads_arena;
frame->dverts_arena = old_dverts_arena; frame->dverts_arena = old_dverts_arena;
frame->dvert_idxs_arena = old_dvert_idxs_arena; frame->dvert_idxs_arena = old_dvert_idxs_arena;
frame->local_world = old_local_world;
} }
frame->cl = G_PrepareCommandList(G_QueueKind_Direct); frame->cl = G_PrepareCommandList(G_QueueKind_Direct);
ResetArena(frame->arena); ResetArena(frame->arena);
@ -1006,8 +1008,8 @@ void V_TickForever(WaveLaneCtx *lane)
Vec2 look_pos = frame->look; Vec2 look_pos = frame->look;
look_pos = RotateVec2(look_pos, InvertRot(rot)); look_pos = RotateVec2(look_pos, InvertRot(rot));
P_Ent *player = P_EntFromKey(local_world->last_frame, V.player_key); P_Ent *player = P_EntFromKey(frame->local_world->last_frame, V.player_key);
P_Ent *guy = P_EntFromKey(local_world->last_frame, player->guy); P_Ent *guy = P_EntFromKey(frame->local_world->last_frame, player->guy);
Vec2 guy_center = P_WorldShapeFromEnt(guy).centroid; Vec2 guy_center = P_WorldShapeFromEnt(guy).centroid;
Vec2 screen_center = MulVec2(frame->screen_dims, 0.5); Vec2 screen_center = MulVec2(frame->screen_dims, 0.5);
target_camera_pos = guy_center; target_camera_pos = guy_center;
@ -1363,12 +1365,13 @@ void V_TickForever(WaveLaneCtx *lane)
// If the server reports it has too many of our commands buffered up, we // If the server reports it has too many of our commands buffered up, we
// slow the rate of prediction. // slow the rate of prediction.
f64 dilation_factor = 0;
{ {
// How many buffered commands of ours we'd like the server to have // How many buffered commands of ours we'd like the server to have
i64 target_buffered_controls_count = 1; i64 target_buffered_controls_count = 1;
f64 rtt_bias_factor = 10.0; f64 rtt_bias_factor = 10.0;
f64 dilation_factor = SmoothstepF64( dilation_factor = SmoothstepF64(
-(SIM_TICKS_PER_SECOND * smoothed_rtt * rtt_bias_factor), -(SIM_TICKS_PER_SECOND * smoothed_rtt * rtt_bias_factor),
(SIM_TICKS_PER_SECOND * smoothed_rtt * rtt_bias_factor), (SIM_TICKS_PER_SECOND * smoothed_rtt * rtt_bias_factor),
target_buffered_controls_count - remote_buffered_controls_count target_buffered_controls_count - remote_buffered_controls_count
@ -1630,7 +1633,7 @@ void V_TickForever(WaveLaneCtx *lane)
} }
predict_world->seed = sim_world->seed; predict_world->seed = sim_world->seed;
P_ClearFrames(predict_world, I64Min, first_predict_tick - 1); P_ClearFrames(predict_world, I64Min, MinI64(first_predict_tick - 1, prev_frame->blend_from_tick - 1));
predict_frame = P_PushFrame(predict_world, P_FrameFromTick(sim_world, first_predict_tick), first_predict_tick); predict_frame = P_PushFrame(predict_world, P_FrameFromTick(sim_world, first_predict_tick), first_predict_tick);
for (i64 predict_tick = first_predict_tick + 1; predict_tick <= last_predict_tick; ++predict_tick) for (i64 predict_tick = first_predict_tick + 1; predict_tick <= last_predict_tick; ++predict_tick)
@ -1833,21 +1836,92 @@ void V_TickForever(WaveLaneCtx *lane)
////////////////////////////// //////////////////////////////
//- Update local world //- Update local world
// TODO: Remove this P_Frame *prev_local_frame = prev_frame->local_world->last_frame;
P_Frame *local_frame = &P_NilFrame; P_Frame *local_frame = &P_NilFrame;
{ {
if (local_world->tiles_hash != predict_world->tiles_hash) if (frame->local_world->tiles_hash != predict_world->tiles_hash)
{ {
local_world->tiles_hash = predict_world->tiles_hash; frame->local_world->tiles_hash = predict_world->tiles_hash;
CopyStructs(local_world->tiles, predict_world->tiles, P_TilesCount); CopyStructs(frame->local_world->tiles, predict_world->tiles, P_TilesCount);
frame->tiles_dirty = 1; frame->tiles_dirty = 1;
} }
local_world->seed = predict_world->seed; frame->local_world->seed = predict_world->seed;
P_ClearFrames(frame->local_world, I64Min, I64Max);
P_ClearFrames(local_world, I64Min, local_world->last_frame->tick - 1); i64 target_blend_dt_ns = frame->dt_ns + frame->dt_ns * dilation_factor;
local_frame = P_PushFrame(local_world, predict_world->last_frame, predict_world->last_frame->tick); i64 blend_dt_ns = frame->dt_ns;
// LogDebugF("First frame: %F, Last frame: %F", FmtSint(local_world->first_frame->tick), FmtSint(local_world->last_frame->tick)); V.target_blend_time_ns += target_blend_dt_ns;
V.blend_time_ns += blend_dt_ns;
// How many ticks back in time should the user thread blend between?
// <Delay> = <USER_INTERP_RATIO> * <Tick interval>
// E.g: At 1.5, the world will render 75ms back in time if the sim runs at 50tps
f32 interp_ratio = TweakFloat("Interpolation ratio", 1.2, 0, 5);
if (predict_to != prev_frame_predict_to)
{
i64 delay_ns = SIM_TICK_INTERVAL_NS * interp_ratio;
V.target_blend_time_ns = predict_world->last_frame->time_ns - delay_ns;
}
f64 blend_to_target_lerp_rate = 0.05;
V.blend_time_ns = LerpI64(V.blend_time_ns, V.target_blend_time_ns, blend_to_target_lerp_rate);
if (AbsI64(V.blend_time_ns - V.target_blend_time_ns) > (SIM_TICK_INTERVAL_NS * interp_ratio * 2))
{
LogDebugF("Blend reset");
V.blend_time_ns = V.target_blend_time_ns;
}
if (TweakBool("Interpolation enabled", 1))
{
P_Frame *left_frame = &P_NilFrame;
P_Frame *right_frame = &P_NilFrame;
for (P_Frame *tmp = predict_world->last_frame; !P_IsFrameNil(tmp); tmp = tmp->prev)
{
if (tmp->time_ns >= V.blend_time_ns && tmp->prev->time_ns <= V.blend_time_ns)
{
right_frame = tmp;
left_frame = tmp->prev;
break;
}
}
if (P_IsFrameNil(left_frame) || P_IsFrameNil(right_frame))
{
right_frame = predict_world->last_frame;
left_frame = right_frame->prev;
}
frame->blend_from_tick = left_frame->tick;
frame->blend_to_tick = right_frame->tick;
local_frame = P_PushFrame(frame->local_world, left_frame, left_frame->tick);
{
f64 blend_t = (f64)(V.blend_time_ns - left_frame->time_ns) / (f64)(right_frame->time_ns - left_frame->time_ns);
for (P_Ent *ent = P_FirstEnt(local_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
{
P_Ent *left = ent;
P_Ent *right = P_EntFromKey(right_frame, ent->key);
ent->exists = LerpF32(left->exists, right->exists, blend_t);
if (!P_IsEntNil(right))
{
ent->health = LerpF32(left->health, right->health, blend_t);
ent->xf.t = LerpVec2(left->xf.t, right->xf.t, blend_t);
ent->xf.r = SlerpVec2(left->xf.r, right->xf.r, blend_t);
ent->walk_time_accum_ns = LerpI64(left->walk_time_accum_ns, right->walk_time_accum_ns, blend_t);
ent->v = LerpVec2(left->v, right->v, blend_t);
ent->w = LerpF32(left->w, right->w, blend_t);
}
}
}
}
else
{
local_frame = P_PushFrame(frame->local_world, predict_world->last_frame, predict_world->last_frame->tick);
}
P_DebugDrawFrame(local_frame); P_DebugDrawFrame(local_frame);
@ -1858,6 +1932,34 @@ void V_TickForever(WaveLaneCtx *lane)
// // TODO: Remove this
// P_Frame *local_frame = &P_NilFrame;
// {
// if (frame->local_world->tiles_hash != predict_world->tiles_hash)
// {
// frame->local_world->tiles_hash = predict_world->tiles_hash;
// CopyStructs(frame->local_world->tiles, predict_world->tiles, P_TilesCount);
// frame->tiles_dirty = 1;
// }
// frame->local_world->seed = predict_world->seed;
// P_ClearFrames(frame->local_world, I64Min, frame->local_world->last_frame->tick - 1);
// local_frame = P_PushFrame(frame->local_world, predict_world->last_frame, predict_world->last_frame->tick);
// // LogDebugF("First frame: %F, Last frame: %F", FmtSint(frame->local_world->first_frame->tick), FmtSint(frame->local_world->last_frame->tick));
// P_DebugDrawFrame(local_frame);
// }
// { // {
// i64 delay_ns = NsFromSeconds(100); // i64 delay_ns = NsFromSeconds(100);
@ -1883,8 +1985,8 @@ void V_TickForever(WaveLaneCtx *lane)
////////////////////////////// //////////////////////////////
//- Query local player //- Query local player
P_Ent *local_player = P_EntFromKey(local_world->last_frame, V.player_key); P_Ent *local_player = P_EntFromKey(frame->local_world->last_frame, V.player_key);
P_Ent *local_guy = P_EntFromKey(local_world->last_frame, local_player->guy); P_Ent *local_guy = P_EntFromKey(frame->local_world->last_frame, local_player->guy);
////////////////////////////// //////////////////////////////
//- Compute crosshair position //- Compute crosshair position
@ -2082,7 +2184,7 @@ void V_TickForever(WaveLaneCtx *lane)
{ {
// TODO: Real world query // TODO: Real world query
P_Shape cursor_shape = P_ShapeFromDesc(.count = 1, .points = { frame->world_cursor }); P_Shape cursor_shape = P_ShapeFromDesc(.count = 1, .points = { frame->world_cursor });
for (P_Ent *ent = P_FirstEnt(local_world->last_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) for (P_Ent *ent = P_FirstEnt(frame->local_world->last_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
{ {
P_Shape ent_shape = P_WorldShapeFromEnt(ent); P_Shape ent_shape = P_WorldShapeFromEnt(ent);
b32 is_hovered = P_CollisionResultFromShapes(ent_shape, cursor_shape).collision_points_count > 0; b32 is_hovered = P_CollisionResultFromShapes(ent_shape, cursor_shape).collision_points_count > 0;
@ -2300,7 +2402,7 @@ void V_TickForever(WaveLaneCtx *lane)
if (bullet->is_bullet) if (bullet->is_bullet)
{ {
// FIXME: Use 'last visible' pos // FIXME: Use 'last visible' pos
P_Ent *old_bullet = P_EntFromKey(local_world->last_frame->prev, bullet->key); P_Ent *old_bullet = P_EntFromKey(prev_local_frame, bullet->key);
Vec2 start = old_bullet->xf.t; Vec2 start = old_bullet->xf.t;
Vec2 end = bullet->xf.t; Vec2 end = bullet->xf.t;
if (P_IsEntNil(old_bullet)) if (P_IsEntNil(old_bullet))
@ -2374,7 +2476,7 @@ void V_TickForever(WaveLaneCtx *lane)
if (bullet->is_bullet && bullet->has_hit) if (bullet->is_bullet && bullet->has_hit)
{ {
// FIXME: Use actual velocity // FIXME: Use actual velocity
P_Ent *old_bullet = P_EntFromKey(local_frame->prev, bullet->key); P_Ent *old_bullet = P_EntFromKey(prev_local_frame, bullet->key);
Vec2 start = old_bullet->xf.t; Vec2 start = old_bullet->xf.t;
Vec2 end = bullet->xf.t; Vec2 end = bullet->xf.t;
if (P_IsEntNil(old_bullet)) if (P_IsEntNil(old_bullet))
@ -4166,10 +4268,10 @@ void V_TickForever(WaveLaneCtx *lane)
UI_BuildLabelF("Predicted world entities: %F", FmtSint(predict_world->last_frame->ents_count)); UI_BuildLabelF("Predicted world entities: %F", FmtSint(predict_world->last_frame->ents_count));
UI_BuildLabelF("Predicted world constraints: %F", FmtSint(predict_world->last_frame->constraints_count)); UI_BuildLabelF("Predicted world constraints: %F", FmtSint(predict_world->last_frame->constraints_count));
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y); UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
UI_BuildLabelF("Local world seed: 0x%F", FmtHex(local_world->seed)); UI_BuildLabelF("Local world seed: 0x%F", FmtHex(frame->local_world->seed));
UI_BuildLabelF("Local world tick: %F", FmtSint(local_world->last_frame->tick)); UI_BuildLabelF("Local world tick: %F", FmtSint(frame->local_world->last_frame->tick));
UI_BuildLabelF("Local world entities: %F", FmtSint(local_world->last_frame->ents_count)); UI_BuildLabelF("Local world entities: %F", FmtSint(frame->local_world->last_frame->ents_count));
UI_BuildLabelF("Local world constraints: %F", FmtSint(local_world->last_frame->constraints_count)); UI_BuildLabelF("Local world constraints: %F", FmtSint(frame->local_world->last_frame->constraints_count));
} }
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y); UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
{ {
@ -4795,7 +4897,7 @@ void V_TickForever(WaveLaneCtx *lane)
G_CopyCpuToTexture( G_CopyCpuToTexture(
frame->cl, frame->cl,
gpu_tiles_res, VEC3I32(0, 0, 0), gpu_tiles_res, VEC3I32(0, 0, 0),
local_world->tiles, VEC3I32(tiles_dims.x, tiles_dims.y, 1), frame->local_world->tiles, VEC3I32(tiles_dims.x, tiles_dims.y, 1),
RNG3I32(VEC3I32(0, 0, 0), VEC3I32(tiles_dims.x, tiles_dims.y, 1)) RNG3I32(VEC3I32(0, 0, 0), VEC3I32(tiles_dims.x, tiles_dims.y, 1))
); );
} }
@ -5175,6 +5277,7 @@ void V_TickForever(WaveLaneCtx *lane)
G_CommitCommandList(frame->cl); G_CommitCommandList(frame->cl);
i32 vsync = !!TweakBool("Vsync", 1); i32 vsync = !!TweakBool("Vsync", 1);
vsync = 1;
UI_EndFrame(ui_frame, vsync); UI_EndFrame(ui_frame, vsync);
} }

View File

@ -228,12 +228,16 @@ Struct(V_Frame)
Embed(V_SharedFrame, shared_frame); Embed(V_SharedFrame, shared_frame);
P_World *local_world;
RandState rand; RandState rand;
NET_Key sim_key; NET_Key sim_key;
NET_Key desired_sim_key; NET_Key desired_sim_key;
f64 predict_tick_accum; f64 predict_tick_accum;
i64 blend_from_tick;
i64 blend_to_tick;
Button held_buttons[Button_COUNT]; Button held_buttons[Button_COUNT];
V_Palette palette; V_Palette palette;
@ -260,6 +264,9 @@ Struct(V_Ctx)
i64 connect_try_ns; i64 connect_try_ns;
i64 target_blend_time_ns;
i64 blend_time_ns;
// Notifications // Notifications
V_Notif *first_notif; V_Notif *first_notif;
V_Notif *last_notif; V_Notif *last_notif;