use a blend time for interpolation

This commit is contained in:
jacob 2024-03-13 13:14:51 -05:00
parent 85ff4d5e2e
commit 8f33957bf9
6 changed files with 142 additions and 42 deletions

View File

@ -273,11 +273,14 @@ typedef u64 umm;
#define I32_MAX (0x7FFFFFFF)
#define I64_MAX (0x7FFFFFFFFFFFFFFFLL)
#define I8_MIN ((i8)-0x80LL)
#define I16_MIN ((i16)-0x8000LL)
#define I32_MIN ((i32)-0x80000000LL)
#define I8_MIN ((i8)-0x80)
#define I16_MIN ((i16)-0x8000)
#define I32_MIN ((i32)-0x80000000)
#define I64_MIN ((i64)-0x8000000000000000LL)
#define PI ((f32)3.14159265358979323846)
#define TAU ((f32)6.28318530717958647693)
/* ========================== *
* Common structs
* ========================== */

View File

@ -9,3 +9,6 @@
#define AUDIO_ENABLED 0
#define VSYNC_ENABLED 0
#define GAME_FPS 30
#define USER_FRAME_LIMIT 300

View File

@ -9,8 +9,6 @@
#include "math.h"
#include "scratch.h"
#define GAME_FPS 30
GLOBAL struct {
b32 shutdown;
struct sys_thread game_thread;

View File

@ -3,9 +3,6 @@
#include "intrinsics.h"
#define PI ((f32)3.14159265358979323846)
#define TAU ((f32)6.28318530717958647693)
/* ========================== *
* Rounding
* ========================== */

View File

@ -19,8 +19,9 @@
#include "sound.h"
#include "mixer.h"
/* NOTE: If vsync is enabled and lower then this will be ignored */
#define USER_FPS 300
/* How far back in time should the user blend between game ticks (in addition
* to the existing 1-tick delay) */
#define USER_INTERP_DELAY 0.005
struct view {
f32 px_per_unit;
@ -35,16 +36,25 @@ struct bind_state {
u32 num_presses; /* How many times was this bind pressed since last frame */
};
struct blend_tick {
struct blend_tick *next;
struct blend_tick *prev;
struct world world;
};
GLOBAL struct {
b32 shutdown;
struct sys_thread user_thread;
struct arena arena;
struct sys_window *window;
struct renderer_canvas *world_canvas;
struct renderer_canvas *screen_canvas;
struct view world_view;
struct world blend_ticks[2];
struct blend_tick *head_free_blend_tick;
struct blend_tick *head_blend_tick;
struct world world;
struct bind_state bind_states[USER_BIND_KIND_COUNT];
@ -67,7 +77,6 @@ GLOBAL struct {
struct v2 screen_mouse;
} L = { 0 }, DEBUG_LVAR(L_user);
/* ========================== *
* Bind state
* ========================== */
@ -125,36 +134,115 @@ INTERNAL struct sys_event_array pull_sys_events(struct arena *arena)
* Game -> user communication
* ========================== */
INTERNAL struct blend_tick *blend_tick_alloc(void)
{
struct blend_tick *bt = NULL;
if (L.head_free_blend_tick) {
bt = L.head_free_blend_tick;
L.head_free_blend_tick = bt->next;
*bt = (struct blend_tick) {
.world = bt->world
};
} else {
bt = arena_push_zero(&L.arena, struct blend_tick);
world_alloc(&bt->world);
}
if (L.head_blend_tick) {
bt->next = L.head_blend_tick;
L.head_blend_tick->prev = bt;
}
L.head_blend_tick = bt;
return bt;
}
INTERNAL void blend_tick_release(struct blend_tick *bt)
{
struct blend_tick *next = bt->next;
struct blend_tick *prev = bt->prev;
/* Remove from list */
if (next) {
next->prev = prev;
}
if (prev) {
prev->next = next;
}
if (bt == L.head_blend_tick) {
L.head_blend_tick = next;
}
/* Add to free list */
bt->next = L.head_free_blend_tick;
bt->prev = NULL;
L.head_free_blend_tick = bt;
}
struct interp_ticks {
struct world *from_tick;
struct world *to_tick;
};
INTERNAL struct interp_ticks pull_interp_ticks(void)
INTERNAL struct interp_ticks pull_ticks(f64 blend_time)
{
/* Function is currently hard-coded for 2 blend ticks */
ASSERT(ARRAY_COUNT(L.blend_ticks) == 2);
__prof;
/* Determine from & to tick from existing ticks */
struct world *from_tick = NULL;
struct world *to_tick = NULL;
if (L.blend_ticks[1].tick_id > L.blend_ticks[0].tick_id) {
from_tick = &L.blend_ticks[0];
to_tick = &L.blend_ticks[1];
} else {
from_tick = &L.blend_ticks[1];
to_tick = &L.blend_ticks[0];
/* Find newest stored tick */
struct world *newest_tick = NULL;
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
if (!newest_tick || bt->world.tick_id > newest_tick->tick_id) {
newest_tick = &bt->world;
}
}
/* Check for new tick from game thread */
u64 latest_tick_id = game_get_latest_tick_id();
if (latest_tick_id > to_tick->tick_id) {
/* Swap pointers */
struct world *temp = from_tick;
from_tick = to_tick;
to_tick = temp;
/* Pull game tick */
game_get_latest_tick(to_tick);
/* Pull new tick from game thread if necessary */
if (!newest_tick || game_get_latest_tick_id() > newest_tick->tick_id) {
struct blend_tick *latest_bt = blend_tick_alloc();
newest_tick = &latest_bt->world;
game_get_latest_tick(newest_tick);
}
/* Find oldest tick */
struct world *oldest_tick = NULL;
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
if (!oldest_tick || bt->world.tick_id < oldest_tick->tick_id) {
oldest_tick = &bt->world;
}
}
/* Find closest ticks to blend time */
struct world *from_tick = oldest_tick;
struct world *to_tick = newest_tick;
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
f64 bt_time = sys_timestamp_seconds(bt->world.tick_published_ts);
if (bt_time < blend_time && bt_time > sys_timestamp_seconds(from_tick->tick_published_ts)) {
from_tick = &bt->world;
}
if (bt_time > blend_time && bt_time < sys_timestamp_seconds(to_tick->tick_published_ts)) {
to_tick = &bt->world;
}
}
/* Free any unused old ticks */
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct blend_tick **bts_to_free = arena_dry_push(scratch.arena, struct blend_tick *);
u64 bts_to_free_count = 0;
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
f64 bt_time = sys_timestamp_seconds(bt->world.tick_published_ts);
if (bt_time < sys_timestamp_seconds(from_tick->tick_published_ts)) {
*arena_push(scratch.arena, struct blend_tick *) = bt;
++bts_to_free_count;
}
}
for (u64 i = 0; i < bts_to_free_count; ++i) {
blend_tick_release(bts_to_free[i]);
}
scratch_end(scratch);
}
return (struct interp_ticks) {
@ -498,7 +586,12 @@ INTERNAL void user_update(void)
* ========================== */
/* Pull ticks */
struct interp_ticks interp_ticks = pull_interp_ticks();
/* FIXME: use real game DT */
f64 blend_time_offset = USER_INTERP_DELAY + (1.0 / GAME_FPS);
f64 blend_time = L.time > blend_time_offset ? L.time - blend_time_offset : 0;
struct interp_ticks interp_ticks = pull_ticks(blend_time);
struct world *t0 = interp_ticks.from_tick;
struct world *t1 = interp_ticks.to_tick;
@ -506,8 +599,14 @@ INTERNAL void user_update(void)
{
__profscope(produce_interpolated_tick);
sys_timestamp_t tick_publish_delta = t1->tick_published_ts - t0->tick_published_ts;
f32 tick_blend = (f32)((sys_timestamp() - t1->tick_published_ts) / (f64)tick_publish_delta);
f64 t0_time = sys_timestamp_seconds(t0->tick_published_ts);
f64 t1_time = sys_timestamp_seconds(t1->tick_published_ts);
f32 tick_blend = 0;
if (t1_time > t0_time) {
tick_blend = (f32)((blend_time - t0_time) / (t1_time - t0_time));
}
tick_blend = clamp_f32(tick_blend, 0.0f, 1.0f);
world_copy_replace(&L.world, t1);
@ -840,7 +939,7 @@ INTERNAL void user_thread_entry_point(void *arg)
(UNUSED)arg;
sys_timestamp_t last_frame_ts = 0;
f64 target_dt = USER_FPS > 0 ? (1.0 / USER_FPS) : 0;
f64 target_dt = USER_FRAME_LIMIT > 0 ? (1.0 / USER_FRAME_LIMIT) : 0;
while (!L.shutdown) {
__profscope(user_update_w_sleep);
@ -852,14 +951,11 @@ INTERNAL void user_thread_entry_point(void *arg)
void user_startup(struct sys_window *window)
{
L.window = window;
sys_window_register_event_callback(L.window, &window_event_callback);
L.arena = arena_alloc(GIGABYTE(64));
L.sys_events_mutex = sys_mutex_alloc();
L.sys_events_arena = arena_alloc(GIGABYTE(64));
world_alloc(&L.blend_ticks[0]);
world_alloc(&L.blend_ticks[1]);
world_alloc(&L.world);
L.world_canvas = renderer_canvas_alloc();
@ -873,6 +969,9 @@ void user_startup(struct sys_window *window)
L.screen_canvas = renderer_canvas_alloc();
L.window = window;
sys_window_register_event_callback(L.window, &window_event_callback);
L.user_thread = sys_thread_init(&user_thread_entry_point, NULL, STR("[P1] User thread"));
}

View File

@ -5,7 +5,7 @@
struct world {
u64 tick_id; /* Starts at 1 */
sys_timestamp_t tick_published_ts;
u64 tick_published_ts;
f64 dt;
f64 time;