custom frame control
There is no browser build of this example yet.
Is it the same as C?
- Desktop:builds The Zig version builds, but it is not compared with C, because the C example does not give the same frame twice: hand-rolled busy-wait loop on GetTime.
- Browser:not checked yet The browser comparison does not cover this example yet.
//! raylib [core] example - custom frame control
//!
//! Zig-API translation of examples/core/core_custom_frame_control.c (raylib 6.2).
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2021-2025 Ramon Santamaria (@raysan5)
//!
//! NOTE: WARNING: This is an example for advanced users willing to have full control over
//! the frame processes. By default, endDrawing() calls the following processes:
//! 1. Draw remaining batch data: rl.gl.drawRenderBatchActive()
//! 2. swapScreenBuffer()
//! 3. Frame time control: waitTime()
//! 4. pollInputEvents()
//!
//! To avoid steps 2, 3 and 4, flag SUPPORT_CUSTOM_FRAME_CONTROL can be enabled in
//! config.h (it requires recompiling raylib). This way those steps are up to the user
//!
//! Note that enabling this flag invalidates some functions:
//! - getFrameTime()
//! - setTargetFPS()
//! - getFPS()
const rl = @import("raylib");
pub fn main() void {
// Initialization
const screen_width = 800;
const screen_height = 450;
rl.initWindow(screen_width, screen_height, "raylib [core] example - custom frame control");
defer rl.closeWindow(); // Close window and OpenGL context
// Custom timming variables
var previous_time = rl.getTime(); // Previous time measure
var delta_time: f32 = 0.0; // Frame time (Update + Draw + Wait time)
var time_counter: f32 = 0.0; // Accumulative time counter (seconds)
var position: f32 = 0.0; // Circle position
var pause = false; // Pause control flag
var target_fps: i32 = 60; // Our initial target fps
// Main game loop
while (!rl.windowShouldClose()) { // Detect window close button or ESC key
// Update
// NOTE: On non web platforms the pollInputEvents just works before the inputs checks
rl.pollInputEvents(); // Poll input events (SUPPORT_CUSTOM_FRAME_CONTROL)
if (rl.isKeyPressed(.space)) pause = !pause;
if (rl.isKeyPressed(.up)) {
target_fps += 20;
} else if (rl.isKeyPressed(.down)) {
target_fps -= 20;
}
if (target_fps < 0) target_fps = 0;
if (!pause) {
position += 200 * delta_time; // We move at 200 pixels per second
if (position >= @as(f32, @floatFromInt(rl.getScreenWidth()))) position = 0;
time_counter += delta_time; // We count time (seconds)
}
// Draw
draw(position, time_counter, target_fps, delta_time);
// NOTE: In case raylib is configured to SUPPORT_CUSTOM_FRAME_CONTROL,
// Events polling, screen buffer swap and frame time control must be managed by the user
rl.swapScreenBuffer(); // Flip the back buffer to screen (front buffer)
var current_time = rl.getTime();
const update_draw_time = current_time - previous_time; // Update + Draw time
if (target_fps > 0) { // We want a fixed frame rate
const wait_time = (1.0 / @as(f32, @floatFromInt(target_fps))) - update_draw_time;
if (wait_time > 0.0) {
rl.waitTime(@as(f32, @floatCast(wait_time)));
current_time = rl.getTime();
delta_time = @floatCast(current_time - previous_time);
}
} else delta_time = @floatCast(update_draw_time); // Framerate could be variable
previous_time = current_time;
}
}
fn draw(position: f32, time_counter: f32, target_fps: i32, delta_time: f32) void {
rl.beginDrawing();
defer rl.endDrawing();
rl.clearBackground(.ray_white);
for (0..@intCast(@divTrunc(rl.getScreenWidth(), 200))) |i| {
rl.drawRectangle(200 * @as(i32, @intCast(i)), 0, 1, rl.getScreenHeight(), .sky_blue);
}
const x: i32 = @intFromFloat(position);
const center_y = @divTrunc(rl.getScreenHeight(), 2);
rl.drawCircle(x, center_y - 25, 50, .red);
rl.drawText(rl.textFormat("{d:0>3.0} ms", .{time_counter * 1000.0}), x - 40, center_y - 100, 20, .maroon);
rl.drawText(rl.textFormat("PosX: {d:0>3.0}", .{position}), x - 50, center_y + 40, 20, .black);
rl.drawText("Circle is moving at a constant 200 pixels/sec,\nindependently of the frame rate.", 10, 10, 20, .dark_gray);
rl.drawText("PRESS SPACE to PAUSE MOVEMENT", 10, rl.getScreenHeight() - 60, 20, .gray);
rl.drawText("PRESS UP | DOWN to CHANGE TARGET FPS", 10, rl.getScreenHeight() - 30, 20, .gray);
rl.drawText(rl.textFormat("TARGET FPS: {d}", .{target_fps}), rl.getScreenWidth() - 220, 10, 20, .lime);
if (delta_time != 0) {
const current_fps: i32 = @intFromFloat(1.0 / delta_time);
rl.drawText(rl.textFormat("CURRENT FPS: {d}", .{current_fps}), rl.getScreenWidth() - 220, 40, 20, .green);
}
}/*******************************************************************************************
*
* raylib [core] example - custom frame control
*
* Example complexity rating: [★★★★] 4/4
*
* NOTE: WARNING: This is an example for advanced users willing to have full control over
* the frame processes. By default, EndDrawing() calls the following processes:
* 1. Draw remaining batch data: rlDrawRenderBatchActive()
* 2. SwapScreenBuffer()
* 3. Frame time control: WaitTime()
* 4. PollInputEvents()
*
* To avoid steps 2, 3 and 4, flag SUPPORT_CUSTOM_FRAME_CONTROL can be enabled in
* config.h (it requires recompiling raylib). This way those steps are up to the user
*
* Note that enabling this flag invalidates some functions:
* - GetFrameTime()
* - SetTargetFPS()
* - GetFPS()
*
* Example originally created with raylib 4.0, last time updated with raylib 4.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2021-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
#include "raylib.h"
//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [core] example - custom frame control");
// Custom timming variables
double previousTime = GetTime(); // Previous time measure
double currentTime = 0.0; // Current time measure
double updateDrawTime = 0.0; // Update + Draw time
double waitTime = 0.0; // Wait time (if target fps required)
float deltaTime = 0.0f; // Frame time (Update + Draw + Wait time)
float timeCounter = 0.0f; // Accumulative time counter (seconds)
float position = 0.0f; // Circle position
bool pause = false; // Pause control flag
int targetFPS = 60; // Our initial target fps
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
#ifndef PLATFORM_WEB // NOTE: On non web platforms the PollInputEvents just works before the inputs checks
PollInputEvents(); // Poll input events (SUPPORT_CUSTOM_FRAME_CONTROL)
#endif
if (IsKeyPressed(KEY_SPACE)) pause = !pause;
if (IsKeyPressed(KEY_UP)) targetFPS += 20;
else if (IsKeyPressed(KEY_DOWN)) targetFPS -= 20;
if (targetFPS < 0) targetFPS = 0;
if (!pause)
{
position += 200*deltaTime; // We move at 200 pixels per second
if (position >= GetScreenWidth()) position = 0;
timeCounter += deltaTime; // We count time (seconds)
}
#ifdef PLATFORM_WEB // NOTE: On web platform for some reason the PollInputEvents only works after the inputs check, so just call it after check all your inputs (on web)
PollInputEvents(); // Poll input events (SUPPORT_CUSTOM_FRAME_CONTROL)
#endif
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
for (int i = 0; i < GetScreenWidth()/200; i++) DrawRectangle(200*i, 0, 1, GetScreenHeight(), SKYBLUE);
DrawCircle((int)position, GetScreenHeight()/2 - 25, 50, RED);
DrawText(TextFormat("%03.0f ms", timeCounter*1000.0f), (int)position - 40, GetScreenHeight()/2 - 100, 20, MAROON);
DrawText(TextFormat("PosX: %03.0f", position), (int)position - 50, GetScreenHeight()/2 + 40, 20, BLACK);
DrawText("Circle is moving at a constant 200 pixels/sec,\nindependently of the frame rate.", 10, 10, 20, DARKGRAY);
DrawText("PRESS SPACE to PAUSE MOVEMENT", 10, GetScreenHeight() - 60, 20, GRAY);
DrawText("PRESS UP | DOWN to CHANGE TARGET FPS", 10, GetScreenHeight() - 30, 20, GRAY);
DrawText(TextFormat("TARGET FPS: %i", targetFPS), GetScreenWidth() - 220, 10, 20, LIME);
if (deltaTime != 0)
{
DrawText(TextFormat("CURRENT FPS: %i", (int)(1.0f/deltaTime)), GetScreenWidth() - 220, 40, 20, GREEN);
}
EndDrawing();
// NOTE: In case raylib is configured to SUPPORT_CUSTOM_FRAME_CONTROL,
// Events polling, screen buffer swap and frame time control must be managed by the user
SwapScreenBuffer(); // Flip the back buffer to screen (front buffer)
currentTime = GetTime();
updateDrawTime = currentTime - previousTime;
if (targetFPS > 0) // We want a fixed frame rate
{
waitTime = (1.0f/(float)targetFPS) - updateDrawTime;
if (waitTime > 0.0)
{
WaitTime((float)waitTime);
currentTime = GetTime();
deltaTime = (float)(currentTime - previousTime);
}
}
else deltaTime = (float)updateDrawTime; // Framerate could be variable
previousTime = currentTime;
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}From raylib 6.2. The original on GitHub.