From 76073fe5fad8799e5850805f2f090af2cb62c741 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 23:41:31 +0300 Subject: Simplify the public event and CPU API Applications now need far less ceremony: hello.c is a 20-line loop that handles REFRESH, CLOSE and QUIT and draws into the surface's own pixels. - Fold GEOMETRY, STATE and FOCUS into one WINDOW event carrying size, scale and state; unchanged values send nothing. - Guarantee REFRESH once a window has a size and after every resize, never at size zero. - Merge DROP_HOVER into DROP with over/leave/data actions and give drag and drop actions enums. - Keep event strings valid until the next wait and drop yasl_event_release; empty or failed waits report NONE. - Replace copy-in presentation with a lent pixel buffer: yasl_cpu_pixels and yasl_cpu_present. - Remove yasl_poll_event, yasl_cpu_read_pixels, unused event fields and comments that restated names. - Wayland: resize floating windows in request_size instead of reporting it unsupported, and stop marking new GPU surfaces as Vulkan so yasl_vulkan_create_surface no longer fails with NATIVE_WINDOW_IN_USE. Also includes the in-progress public API refactor that was uncommitted in the tree (removal of the app/loop layer, new surface, GL and Vulkan entry points). Co-Authored-By: Claude Opus 5.5 --- examples/game.c | 403 +++++++------------------------------------------------- 1 file changed, 48 insertions(+), 355 deletions(-) (limited to 'examples/game.c') diff --git a/examples/game.c b/examples/game.c index 33c2f6e..79beb7c 100644 --- a/examples/game.c +++ b/examples/game.c @@ -1,360 +1,53 @@ -/** - * @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 pointer mode (YASL_POINTER_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 +/* GPU example: the application owns context binding, drawing and swap pacing. */ +#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; - } - } - } +typedef void (*clear_color_fn)(float, float, float, float); +typedef void (*clear_fn)(unsigned); +typedef void (*viewport_fn)(int, int, int, int); + +int main(int argc, char **argv) +{ + bool smoke = argc == 2 && !strcmp(argv[1], "--smoke-test"); + yasl_surface *s = yasl_gl_surface_create("YASL OpenGL", 800, 600, 0, NULL, NULL); + if (!s || !yasl_gl_make_current(s)) { + const char *error; + yasl_get_error(&error); + fprintf(stderr, "%s\n", error); + yasl_shutdown(); + return 1; + } + clear_color_fn clear_color = (clear_color_fn)yasl_gl_get_proc_address("glClearColor"); + clear_fn clear = (clear_fn)yasl_gl_get_proc_address("glClear"); + viewport_fn viewport = (viewport_fn)yasl_gl_get_proc_address("glViewport"); + if (!clear_color || !clear || !viewport) { + yasl_shutdown(); + return 1; + } + yasl_gl_swap_interval(s, 1); + int result = 0, frames = 0; + for (bool running = true; running;) { + yasl_event e; + int n; + while ((n = yasl_wait_event(&e, 0)) > 0) { + if (e.type == YASL_EVENT_CLOSE || e.type == YASL_EVENT_QUIT || + (e.type == YASL_EVENT_KEY && e.key.key == YASL_KEY_ESCAPE)) running = false; + if (e.type == YASL_EVENT_DISPLAY_LOST) { result = 1; running = false; } } - } -} - -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 enum yasl_app_result on_init(void *ud, struct yasl_display *d) -{ - struct game_state *game = ud; - game->display = d; - - struct yasl_window_spec spec = { - .title = "YASL 3D Game Loop (Approach A)", - .size_logical = {800, 600}, - }; - game->surface = yasl_surface_create_toplevel(d, &spec); - if (!game->surface) return YASL_APP_ERROR; - - /* Drive continuous 60/144Hz ticks */ - if (!yasl_surface_set_pacing(game->surface, YASL_PACING_CONTINUOUS)) - return YASL_APP_ERROR; - - 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 enum yasl_app_result 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_RELEASE; - - 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 pointer mode; unsupported backends stay normal. */ - game->relative_mouse = !game->relative_mouse && - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_RELATIVE); - if (!game->relative_mouse) - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_NORMAL); - } - break; - case YASL_KEY_ESCAPE: - if (ev->key.action == YASL_KEY_PRESS) { - if (game->relative_mouse) { - game->relative_mouse = false; - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_NORMAL); - } else { - return YASL_APP_QUIT; - } - } - break; - default: - break; - } - break; - } - - case YASL_EVENT_POINTER_MOTION: - if (game->relative_mouse) { - /* Smooth first-person camera orbit */ - game->cam_yaw += (float)ev->pointer.delta.x * 0.005f; - game->cam_pitch += (float)ev->pointer.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_POINTER_DOWN: - if (!game->relative_mouse) { - /* Click window to capture cursor */ - game->relative_mouse = - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_RELATIVE); - } - break; - - case YASL_EVENT_WINDOW_CLOSE: - return YASL_APP_QUIT; - - default: - break; - } - - return YASL_APP_CONTINUE; -} - -static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) -{ - (void)s; - struct game_state *game = ud; - int w = ctx->target.size.x; - int h = ctx->target.size.y; - uint32_t *pix = ctx->target.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 */ - if (!yasl_frame_present(ctx, NULL, 0)) - yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); -} - -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); + if (n < 0) { result = 1; break; } + int w, h; + yasl_surface_get_framebuffer_size(s, &w, &h); + viewport(0, 0, w, h); + double t = yasl_time_ns() / 1e9; + clear_color((float)(0.3 + 0.2 * sin(t)), 0.2f, 0.4f, 1); + clear(0x00004000u); + if (!yasl_gl_swap_buffers(s)) { result = 1; break; } + if (smoke && ++frames == 3) break; + } + yasl_gl_make_current(NULL); + yasl_shutdown(); + return result; } -- cgit v1.2.3