diff options
| author | 3gg <3gg@shellblade.net> | 2026-05-02 14:35:43 -0700 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2026-05-02 14:35:43 -0700 |
| commit | 6482f3995baf9515158d999db925ef35158cfba5 (patch) | |
| tree | 17d274b78038c7a06ce0afe9f30d7416fa9f07ef /simloop/src | |
| parent | 48422e313b31b79d76dd8f027b4d934994168859 (diff) | |
Throttle rendering to reduce CPU usage
Diffstat (limited to 'simloop/src')
| -rw-r--r-- | simloop/src/simloop.c | 84 |
1 files changed, 24 insertions, 60 deletions
diff --git a/simloop/src/simloop.c b/simloop/src/simloop.c index b8547fd..d231ab3 100644 --- a/simloop/src/simloop.c +++ b/simloop/src/simloop.c | |||
| @@ -20,87 +20,51 @@ Simloop simloop_make(const SimloopArgs* args) { | |||
| 20 | .ddt = ddt_from_fps(args->update_fps), | 20 | .ddt = ddt_from_fps(args->update_fps), |
| 21 | .time = 0, | 21 | .time = 0, |
| 22 | }, | 22 | }, |
| 23 | .render = | 23 | .render_ddt = ddt_from_fps(args->max_render_fps), |
| 24 | (SimloopTimeline){ | ||
| 25 | .ddt = ddt_from_fps(args->max_render_fps), | ||
| 26 | .time = 0, | ||
| 27 | }, | ||
| 28 | .percent_frame = 0., | ||
| 29 | .first_iter = true, | ||
| 30 | }; | 24 | }; |
| 31 | } | 25 | } |
| 32 | 26 | ||
| 33 | static bool step_update(const Simloop* sim, SimloopTimeline* timeline) { | 27 | void simloop_update(Simloop* sim, simloop_time_t dt, SimloopOut* out) { |
| 34 | assert(sim); | 28 | assert(sim); |
| 35 | assert(timeline); | 29 | assert(out); |
| 36 | assert(timeline->ddt > 0); | 30 | |
| 31 | sim->clock += dt; | ||
| 37 | 32 | ||
| 33 | // Simulation update. | ||
| 38 | // If the update falls behind the clock, we advance by a single ddt increment | 34 | // If the update falls behind the clock, we advance by a single ddt increment |
| 39 | // per loop iteration here and give it a chance to catch up over subsequent | 35 | // per loop iteration here and give it a chance to catch up over subsequent |
| 40 | // iterations. | 36 | // iterations. |
| 41 | // This has the implication that percent_frame can fall out of range (>1) if | 37 | // This has the implication that percent_frame can fall out of range (>1) if |
| 42 | // we are not careful with how it is defined. See the general update function | 38 | // we are not careful with how it is defined. See the general update function |
| 43 | // below. | 39 | // below. |
| 44 | const simloop_time_t dt = sim->clock - timeline->time; | 40 | const simloop_time_t delta = sim->clock - sim->update.time; |
| 45 | const bool should_step = dt >= timeline->ddt; | 41 | const bool update_this_tick = delta >= sim->update.ddt; |
| 46 | timeline->time += should_step ? timeline->ddt : 0; | 42 | sim->update.time += update_this_tick ? sim->update.ddt : 0; |
| 47 | return should_step; | ||
| 48 | } | ||
| 49 | |||
| 50 | static bool step_render(const Simloop* sim, SimloopTimeline* timeline) { | ||
| 51 | assert(sim); | ||
| 52 | assert(timeline); | ||
| 53 | |||
| 54 | bool render = false; | ||
| 55 | if (timeline->ddt > 0) { | ||
| 56 | render = step_update(sim, timeline); | ||
| 57 | } else { | ||
| 58 | render = timeline->time < sim->clock; | ||
| 59 | timeline->time = sim->clock; | ||
| 60 | } | ||
| 61 | return render; | ||
| 62 | } | ||
| 63 | |||
| 64 | void simloop_update(Simloop* sim, simloop_time_t dt, SimloopOut* out) { | ||
| 65 | assert(sim); | ||
| 66 | assert(out); | ||
| 67 | |||
| 68 | sim->clock += dt; | ||
| 69 | |||
| 70 | // Simulation update. | ||
| 71 | const bool update_this_tick = step_update(sim, &sim->update); | ||
| 72 | 43 | ||
| 73 | // Simulation render. | 44 | // Loop-state update. |
| 74 | const bool render_this_tick = | 45 | sim->frame += (update_this_tick ? 1 : 0); |
| 75 | step_render(sim, &sim->render) || | ||
| 76 | sim->first_iter; // Trigger an initial render on the first frame. | ||
| 77 | 46 | ||
| 78 | // Interpolator for smooth animation. | 47 | // Interpolator for smooth animation. |
| 79 | // If rendering is not frame-rate capped, then its timeline should always be | ||
| 80 | // at least as recent as the update's. Otherwise, it is possible for the | ||
| 81 | // rendering timeline to be behind. | ||
| 82 | // If the update falls behind the clock, then percent_frame can fall out of | 48 | // If the update falls behind the clock, then percent_frame can fall out of |
| 83 | // range (>1) if we are not careful. We impose that it is strictly never >1 | 49 | // range (>1) if we are not careful. We impose that it is strictly never >1 |
| 84 | // to account for this case. | 50 | // to account for this case. |
| 85 | assert(sim->update.ddt > 0); | 51 | assert(sim->update.ddt > 0); |
| 86 | assert( | 52 | assert(sim->update.time <= sim->clock); |
| 87 | (sim->render.ddt == 0) ? (sim->update.time <= sim->render.time) : true); | 53 | out->percent_frame = min( |
| 88 | sim->percent_frame = | 54 | 1., (double)(sim->clock - sim->update.time) / (double)sim->update.ddt); |
| 89 | (sim->render.time >= sim->update.time) | 55 | assert((0. <= out->percent_frame) && (out->percent_frame <= 1.)); |
| 90 | ? min(1., ((double)(sim->render.time - sim->update.time) / | 56 | |
| 91 | (double)sim->update.ddt)) | 57 | // Render frame rate throttle. |
| 92 | : sim->percent_frame; | 58 | // Note that if no max render fps is given, then render_ddt is 0. The logic |
| 93 | assert((0. <= sim->percent_frame) && (sim->percent_frame <= 1.)); | 59 | // works for both render_ddt>0 and =0. |
| 94 | 60 | // Need to be careful with subtraction since the quantities are unsigned. | |
| 95 | // Loop state update. | 61 | // Subtract an epsilon to account for delays in thread scheduling. |
| 96 | sim->frame += (update_this_tick ? 1 : 0); | 62 | static const simloop_time_t eps = 50'000; // 50us |
| 97 | sim->first_iter = false; | 63 | out->throttle = |
| 64 | (sim->render_ddt > (dt - eps)) ? (sim->render_ddt - eps - dt) : 0; | ||
| 98 | 65 | ||
| 99 | out->frame = sim->frame; | 66 | out->frame = sim->frame; |
| 100 | out->render_elapsed = sim->render.time; | ||
| 101 | out->update_elapsed = sim->update.time; | 67 | out->update_elapsed = sim->update.time; |
| 102 | out->update_dt = sim->update.ddt; | 68 | out->update_dt = sim->update.ddt; |
| 103 | out->percent_frame = sim->percent_frame; | ||
| 104 | out->should_update = update_this_tick; | 69 | out->should_update = update_this_tick; |
| 105 | out->should_render = render_this_tick; | ||
| 106 | } | 70 | } |
