/** * @file game.c * A continuous 60/144+ FPS game loop demonstration built on YASL Approach A. * * Demonstrates: * - Continuous display-synchronized ticks (YASL_PACING_CONTINUOUS) * - 3D software rendering (rotating shaded cube + starfield) * - WASD continuous player movement & key state tracking * - Relative mouse mode (YASL_MOUSE_RELATIVE) for smooth first-person camera orbit * - Spacebar toggles pointer lock (Relative vs Normal cursor) * - Real-time FPS counter and frame latency display * - Dynamic window resizing with automatic aspect ratio adjustment */ #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #define NUM_STARS 256 #define FOV_SCALE 320.0f struct star { float x, y, z; }; struct vec3 { float x, y, z; }; struct game_state { struct yasl_display *display; struct yasl_surface *surface; /* Camera & controls */ float cam_x, cam_y, cam_z; float cam_yaw, cam_pitch; bool key_w, key_a, key_s, key_d; bool key_q, key_e; bool relative_mouse; /* Simulation objects */ float cube_rot_x, cube_rot_y; struct star stars[NUM_STARS]; /* FPS & timing stats */ uint64_t last_stat_time_ns; int frame_count; float current_fps; float avg_frame_time_ms; }; /* 8x8 bitmap font for overlay HUD */ static const uint8_t font8x8_digits[10][8] = { {0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00}, /* 0 */ {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00}, /* 1 */ {0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00}, /* 2 */ {0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00}, /* 3 */ {0x0C,0x1C,0x34,0x64,0x7E,0x04,0x04,0x00}, /* 4 */ {0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00}, /* 5 */ {0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00}, /* 6 */ {0x7E,0x06,0x0C,0x18,0x30,0x30,0x30,0x00}, /* 7 */ {0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00}, /* 8 */ {0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00}, /* 9 */ }; static void draw_digit(uint32_t *pix, int w, int h, int px, int py, int digit, uint32_t col, int s) { if (digit < 0 || digit > 9) return; const uint8_t *glyph = font8x8_digits[digit]; for (int y = 0; y < 8; y++) { uint8_t row = glyph[y]; for (int x = 0; x < 8; x++) { if (row & (0x80 >> x)) { for (int dy = 0; dy < s; dy++) { int yy = py + y * s + dy; if (yy < 0 || yy >= h) continue; for (int dx = 0; dx < s; dx++) { int xx = px + x * s + dx; if (xx >= 0 && xx < w) pix[(size_t)yy * w + xx] = col; } } } } } } static void draw_number(uint32_t *pix, int w, int h, int px, int py, int num, uint32_t col, int scale) { char buf[16]; snprintf(buf, sizeof(buf), "%d", num); for (int i = 0; buf[i]; i++) { draw_digit(pix, w, h, px + i * 8 * scale, py, buf[i] - '0', col, scale); } } static void draw_line(uint32_t *pix, int w, int h, int x0, int y0, int x1, int y1, uint32_t col) { int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; int err = dx + dy, e2; while (1) { if (x0 >= 0 && x0 < w && y0 >= 0 && y0 < h) { pix[(size_t)y0 * w + x0] = col; } if (x0 == x1 && y0 == y1) break; e2 = 2 * err; if (e2 >= dy) { err += dy; x0 += sx; } if (e2 <= dx) { err += dx; y0 += sy; } } } static struct vec3 rotate_vertex(struct vec3 v, float rx, float ry) { /* Rotate around Y */ float cy = cosf(ry), sy = sinf(ry); float x1 = v.x * cy + v.z * sy; float z1 = -v.x * sy + v.z * cy; /* Rotate around X */ float cx = cosf(rx), sx = sinf(rx); float y2 = v.y * cx - z1 * sx; float z2 = v.y * sx + z1 * cx; return (struct vec3){x1, y2, z2}; } static bool project_point(struct vec3 p, int w, int h, int *out_x, int *out_y) { if (p.z <= 0.2f) return false; float inv_z = FOV_SCALE / p.z; *out_x = (int)(w * 0.5f + p.x * inv_z); *out_y = (int)(h * 0.5f - p.y * inv_z); return true; } static int on_init(void *ud, struct yasl_display *d) { struct game_state *game = ud; game->display = d; struct yasl_surface_spec spec = { .title = "YASL 3D Game Loop (Approach A)", .size = {800, 600}, }; game->surface = yasl_surface_create_toplevel(d, &spec); if (!game->surface) return YASL_APP_QUIT; /* Drive continuous 60/144Hz ticks */ yasl_surface_set_pacing(game->surface, YASL_PACING_CONTINUOUS); game->cam_z = 3.5f; game->relative_mouse = false; /* Initialize stars */ for (int i = 0; i < NUM_STARS; i++) { game->stars[i].x = ((float)(rand() % 2000) - 1000.0f) / 100.0f; game->stars[i].y = ((float)(rand() % 2000) - 1000.0f) / 100.0f; game->stars[i].z = ((float)(rand() % 1000) + 10.0f) / 50.0f; } game->last_stat_time_ns = yasl_loop_now_ns(); return YASL_APP_CONTINUE; } static int on_event(void *ud, const struct yasl_event *ev) { struct game_state *game = ud; switch (ev->type) { case YASL_EVENT_KEY: { bool down = (ev->key.action == YASL_KEY_PRESS || ev->key.action == YASL_KEY_REPEAT); if (ev->key.action == YASL_KEY_RELEASE) down = false; switch (ev->key.key) { case YASL_KEY_W: game->key_w = down; break; case YASL_KEY_S: game->key_s = down; break; case YASL_KEY_A: game->key_a = down; break; case YASL_KEY_D: game->key_d = down; break; case YASL_KEY_Q: game->key_q = down; break; case YASL_KEY_E: game->key_e = down; break; case YASL_KEY_SPACE: if (ev->key.action == YASL_KEY_PRESS) { /* Toggle relative mouse mode */ game->relative_mouse = !game->relative_mouse; yasl_surface_set_mouse_mode(game->surface, game->relative_mouse ? YASL_MOUSE_RELATIVE : YASL_MOUSE_NORMAL); } break; case YASL_KEY_ESCAPE: if (ev->key.action == YASL_KEY_PRESS) { if (game->relative_mouse) { game->relative_mouse = false; yasl_surface_set_mouse_mode(game->surface, YASL_MOUSE_NORMAL); } else { return YASL_APP_QUIT; } } break; default: break; } break; } case YASL_EVENT_MOUSE_MOTION: if (game->relative_mouse) { /* Smooth first-person camera orbit */ game->cam_yaw += (float)ev->mouse_motion.delta.x * 0.005f; game->cam_pitch += (float)ev->mouse_motion.delta.y * 0.005f; if (game->cam_pitch > 1.4f) game->cam_pitch = 1.4f; if (game->cam_pitch < -1.4f) game->cam_pitch = -1.4f; } break; case YASL_EVENT_MOUSE_BUTTON: if (ev->mouse_button.pressed && !game->relative_mouse) { /* Click window to capture cursor */ game->relative_mouse = true; yasl_surface_set_mouse_mode(game->surface, YASL_MOUSE_RELATIVE); } break; case YASL_EVENT_WINDOW_CLOSE_REQUEST: return YASL_APP_QUIT; default: break; } return YASL_APP_CONTINUE; } static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame_context *ctx) { (void)s; struct game_state *game = ud; int w = ctx->size.x; int h = ctx->size.y; uint32_t *pix = ctx->cpu.pixels; if (!pix || w <= 0 || h <= 0) return; /* Update game simulation */ float speed = 0.04f; float forward_x = sinf(game->cam_yaw); float forward_z = cosf(game->cam_yaw); float right_x = cosf(game->cam_yaw); float right_z = -sinf(game->cam_yaw); if (game->key_w) { game->cam_x -= forward_x * speed; game->cam_z -= forward_z * speed; } if (game->key_s) { game->cam_x += forward_x * speed; game->cam_z += forward_z * speed; } if (game->key_a) { game->cam_x -= right_x * speed; game->cam_z -= right_z * speed; } if (game->key_d) { game->cam_x += right_x * speed; game->cam_z += right_z * speed; } if (game->key_q) { game->cam_y -= speed; } if (game->key_e) { game->cam_y += speed; } game->cube_rot_x += 0.015f; game->cube_rot_y += 0.025f; /* Clear screen to deep space dark blue */ memset(pix, 0x08, (size_t)w * h * sizeof(uint32_t)); /* 1. Render 3D Starfield */ for (int i = 0; i < NUM_STARS; i++) { game->stars[i].z -= 0.05f; if (game->stars[i].z <= 0.2f) game->stars[i].z += 20.0f; struct vec3 sp = {game->stars[i].x - game->cam_x, game->stars[i].y - game->cam_y, game->stars[i].z}; int sx, sy; if (project_point(sp, w, h, &sx, &sy) && sx >= 0 && sx < w && sy >= 0 && sy < h) { uint8_t brightness = (uint8_t)(255.0f * (1.0f - sp.z / 20.0f)); uint32_t star_col = 0xff000000 | (brightness << 16) | (brightness << 8) | brightness; pix[(size_t)sy * w + sx] = star_col; } } /* 2. Render 3D Cube */ static const struct vec3 cube_vertices[8] = { {-1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f}, { 1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f}, {-1.0f, -1.0f, 1.0f}, { 1.0f, -1.0f, 1.0f}, { 1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f}, }; static const int cube_edges[12][2] = { {0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}, }; int proj_x[8], proj_y[8]; bool valid[8]; for (int i = 0; i < 8; i++) { struct vec3 rot = rotate_vertex(cube_vertices[i], game->cube_rot_x, game->cube_rot_y); rot.x -= game->cam_x; rot.y -= game->cam_y; rot.z += game->cam_z; valid[i] = project_point(rot, w, h, &proj_x[i], &proj_y[i]); } uint32_t cyan = 0xff00ffff; for (int i = 0; i < 12; i++) { int u = cube_edges[i][0]; int v = cube_edges[i][1]; if (valid[u] && valid[v]) { draw_line(pix, w, h, proj_x[u], proj_y[u], proj_x[v], proj_y[v], cyan); } } /* 3. Center crosshair in relative mouse mode */ if (game->relative_mouse) { int cx = w / 2, cy = h / 2; draw_line(pix, w, h, cx - 6, cy, cx + 6, cy, 0xffffffff); draw_line(pix, w, h, cx, cy - 6, cx, cy + 6, 0xffffffff); } /* 4. FPS counter calculation */ game->frame_count++; uint64_t now = yasl_loop_now_ns(); if (now - game->last_stat_time_ns >= 1000000000ull) { uint64_t elapsed = now - game->last_stat_time_ns; game->current_fps = (float)game->frame_count * 1000000000.0f / (float)elapsed; game->avg_frame_time_ms = 1000.0f / (game->current_fps > 0 ? game->current_fps : 1.0f); game->frame_count = 0; game->last_stat_time_ns = now; } /* Draw FPS overlay digits in top-left */ int scale = ctx->scale_120 >= 240 ? 2 : 1; draw_number(pix, w, h, 16 * scale, 16 * scale, (int)game->current_fps, 0xff00ff00, scale); /* Submit frame */ yasl_frame_present_cpu(ctx, NULL, 0); } int main(void) { static struct game_state game; struct yasl_app_desc desc = { .app_id = "org.yasl.game", .backend = YASL_BACKEND_AUTO, .userdata = &game, .init = on_init, .event = on_event, .frame = on_frame, }; return yasl_run(&desc); }