40 session_should_exit_fn
should_exit, session_capture_fn capture_cb,
41 session_sleep_for_frame_fn sleep_cb, session_keyboard_handler_fn keyboard_handler,
45 SET_ERRNO(ERROR_INVALID_PARAM,
"session_render_loop: display context is NULL");
46 return ERROR_INVALID_PARAM;
50 SET_ERRNO(ERROR_INVALID_PARAM,
"session_render_loop: should_exit callback is NULL");
51 return ERROR_INVALID_PARAM;
55 if (!capture && !capture_cb) {
56 SET_ERRNO(ERROR_INVALID_PARAM,
"session_render_loop: must provide either capture context or capture callback");
57 return ERROR_INVALID_PARAM;
60 if (capture && capture_cb) {
61 SET_ERRNO(ERROR_INVALID_PARAM,
"session_render_loop: cannot provide both capture context and capture callback");
62 return ERROR_INVALID_PARAM;
66 if (capture_cb && !sleep_cb) {
67 SET_ERRNO(ERROR_INVALID_PARAM,
"session_render_loop: capture_cb requires sleep_cb (must provide both or neither)");
68 return ERROR_INVALID_PARAM;
71 if (sleep_cb && !capture_cb) {
72 SET_ERRNO(ERROR_INVALID_PARAM,
"session_render_loop: sleep_cb requires capture_cb (must provide both or neither)");
73 return ERROR_INVALID_PARAM;
77 uint64_t snapshot_start_time_ns = 0;
78 bool snapshot_done =
false;
79 bool first_frame_rendered =
false;
80 bool snapshot_mode = GET_OPTION(snapshot_mode);
83 bool initial_paused_frame_rendered =
false;
84 bool was_paused =
false;
85 bool is_paused =
false;
92 bool keyboard_enabled =
false;
95 asciichat_error_t kb_result = keyboard_init();
96 if (kb_result == ASCIICHAT_OK) {
97 keyboard_enabled =
true;
98 log_debug(
"Keyboard input enabled (TTY mode)");
100 log_debug(
"Failed to initialize keyboard input (%s) - will attempt fallback", asciichat_error_string(kb_result));
102 keyboard_enabled =
true;
107 bool is_synchronous = (capture != NULL);
110 uint64_t frame_count = 0;
111 uint64_t frame_start_ns = 0;
112 uint64_t frame_to_render_ns = 0;
124 image_t *image = NULL;
125 uint64_t capture_start_ns = 0;
126 uint64_t capture_end_ns = 0;
127 uint64_t pre_convert_ns = 0;
128 uint64_t post_convert_ns = 0;
130 if (is_synchronous) {
139 if (!was_paused && is_paused) {
140 initial_paused_frame_rendered =
true;
141 log_debug(
"Media paused, enabling keyboard polling");
145 if (was_paused && !is_paused) {
146 initial_paused_frame_rendered =
false;
147 log_debug(
"Media unpaused, resuming frame capture");
149 was_paused = is_paused;
152 if (is_paused && initial_paused_frame_rendered) {
154 uint64_t idle_sleep_ns = (uint64_t)(NS_PER_SEC_INT / GET_OPTION(fps));
155 platform_sleep_ns(idle_sleep_ns);
159 if (keyboard_handler) {
160 keyboard_key_t key = keyboard_read_nonblocking();
161 if (key != KEY_NONE) {
168 keyboard_handler(capture, key, user_data);
175 static uint64_t max_retries = 0;
176 uint64_t loop_retry_count = 0;
177 uint64_t capture_elapsed_ns = 0;
185 log_debug_every(3 * US_PER_SEC_INT,
"RENDER[%lu]: Starting frame read", frame_count);
187 log_debug_every(3 * US_PER_SEC_INT,
"RENDER[%lu]: Frame read done, image=%p", frame_count, (
void *)image);
193 log_info(
"Media source reached end of file");
205 if (loop_retry_count <= 1 || frame_count % 100 == 0) {
206 if (loop_retry_count == 1 && frame_count > 0) {
207 log_debug_every(500 * US_PER_MS_INT,
"FRAME_WAIT: retry at frame %lu (waited %.1f ms so far)",
208 frame_count, (
double)capture_elapsed_ns / NS_PER_MS);
213 if (loop_retry_count > max_retries) {
214 max_retries = loop_retry_count;
222 if (loop_retry_count > 0) {
223 double wait_ms = (double)capture_elapsed_ns / NS_PER_MS;
224 log_debug_every(US_PER_SEC_INT,
"FRAME_OBTAINED: after %lu retries, waited %.1f ms", loop_retry_count,
242 if (frame_count % 30 == 0) {
243 double capture_ms = (double)capture_elapsed_ns / NS_PER_MS;
244 log_dev_every(5 * US_PER_SEC_INT,
"PROFILE[%lu]: CAPTURE=%.2f ms", frame_count, capture_ms);
249 if (!is_paused && frame_count == 1 && GET_OPTION(pause) && source) {
253 log_debug(
"Paused media source after first frame");
259 image = capture_cb(user_data);
273 uint64_t conversion_elapsed_ns = post_convert_ns - pre_convert_ns;
277 bool is_paused_frame = initial_paused_frame_rendered && is_paused;
280 bool output_paused_frame = snapshot_mode && is_paused_frame;
286 bool should_write =
true;
287 uint64_t pre_render_ns = 0, post_render_ns = 0;
292 START_TIMER(
"render_frame");
302 uint64_t render_elapsed_ns = STOP_TIMER(
"render_frame");
307 if (frame_count % 30 == 0) {
308 double total_frame_time_ms = (double)frame_to_render_ns / NS_PER_MS;
309 log_dev(
"ACTUAL_TIME[%lu]: Total frame time from start to render complete: %.1f ms", frame_count,
310 total_frame_time_ms);
314 if (frame_count % 150 == 0) {
315 double conversion_ms = (double)conversion_elapsed_ns / NS_PER_MS;
316 double render_ms = (double)render_elapsed_ns / NS_PER_MS;
317 log_dev_every(5 * US_PER_SEC_INT,
"PROFILE[%lu]: CONVERT=%.2f ms, RENDER=%.2f ms", frame_count, conversion_ms,
326 if (keyboard_enabled && keyboard_handler) {
327 START_TIMER(
"keyboard_read_%lu", (
unsigned long)frame_count);
328 keyboard_key_t key = keyboard_read_nonblocking();
329 double keyboard_elapsed_ns = STOP_TIMER(
"keyboard_read_%lu", (
unsigned long)frame_count);
330 if (keyboard_elapsed_ns >= 0.0) {
331 char _duration_str[32];
333 log_dev(
"RENDER[%lu] Keyboard read complete (key=%d) in %s", (
unsigned long)frame_count, key, _duration_str);
335 if (key != KEY_NONE) {
343 keyboard_handler(capture, key, user_data);
348 if (snapshot_mode && !first_frame_rendered) {
350 first_frame_rendered =
true;
351 log_debug(
"Snapshot mode: first frame rendered, timer started");
355 if (snapshot_mode && !snapshot_done && first_frame_rendered) {
357 double elapsed_sec = time_ns_to_s(
time_elapsed_ns(snapshot_start_time_ns, current_time_ns));
358 double snapshot_delay = GET_OPTION(snapshot_delay);
360 log_debug_every(US_PER_SEC_INT,
"SNAPSHOT_DELAY_CHECK: elapsed=%.2f delay=%.2f", elapsed_sec, snapshot_delay);
364 if (elapsed_sec >= snapshot_delay) {
371 log_info(
"Snapshot delay %.2f seconds elapsed, exiting", snapshot_delay);
372 snapshot_done =
true;
377 if (snapshot_mode && (snapshot_done || output_paused_frame)) {
378 SAFE_FREE(ascii_frame);
382 SAFE_FREE(ascii_frame);
389 uint64_t prestart_ms =
390 (capture_start_ns > frame_start_ns) ? (capture_start_ns - frame_start_ns) / NS_PER_MS_INT : 0;
391 uint64_t capture_ms =
392 (capture_end_ns > capture_start_ns) ? (capture_end_ns - capture_start_ns) / NS_PER_MS_INT : 0;
393 uint64_t convert_ms = conversion_elapsed_ns / NS_PER_MS_INT;
395 (post_render_ns > pre_render_ns && post_render_ns > 0) ? (post_render_ns - pre_render_ns) / NS_PER_MS_INT : 0;
397 (frame_end_render_ns > frame_start_ns) ? (frame_end_render_ns - frame_start_ns) / NS_PER_MS_INT : 0;
400 if (frame_count % 5 == 0) {
402 "PHASE_BREAKDOWN[%lu]: prestart=%llu ms, capture=%llu ms, convert=%llu ms, render=%llu ms (total=%llu ms)",
403 frame_count, prestart_ms, capture_ms, convert_ms, render_ms, total_ms);
408 if (snapshot_mode && snapshot_done) {
425 if (is_synchronous && capture) {
427 if (target_fps > 0) {
429 uint64_t frame_elapsed_ns =
time_elapsed_ns(frame_start_ns, frame_end_ns);
430 uint64_t frame_target_ns = NS_PER_SEC_INT / target_fps;
432 log_dev(
"RENDER[%lu] TIMING_TOTAL: frame_time_ms=%.2f target_ms=%.2f", frame_count,
433 (
double)frame_elapsed_ns / NS_PER_MS, (
double)frame_target_ns / NS_PER_MS);
437 if (frame_elapsed_ns < frame_target_ns) {
438 uint64_t sleep_ns = frame_target_ns - frame_elapsed_ns;
440 if (sleep_ns > 500 * US_PER_MS_INT) {
441 platform_sleep_ns((sleep_ns - 500 * US_PER_MS_INT));
458 if (keyboard_enabled) {
460 log_debug(
"Keyboard input disabled");