Compare commits
10
Commits
0d0590a4d7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
842c36fde6 | ||
|
|
d76767a64a | ||
|
|
129b5339e1 | ||
|
|
55fc4958f7 | ||
|
|
db05df4227 | ||
|
|
5ad0ddfbc0 | ||
|
|
55055a042d | ||
|
|
ddaeafc14f | ||
|
|
c4bed2f596 | ||
|
|
4e7049f49a |
@@ -1,4 +1,201 @@
|
||||
# skele
|
||||
|
||||
Generic game framework written in C99.
|
||||
[](https://opensource.org/licenses/MPL-2.0)
|
||||
|
||||
`skele` is a lightweight, generic game framework written in C99. It provides a **platform-agnostic core** (clock, video, input) split cleanly from OS/backend-specific implementations, so games and engines built on it stay portable without fighting the framework's opinions.
|
||||
|
||||
It pairs naturally with [`stk`](https://git.forlornoutpost.ca/anth64/stk) for hot-reloadable game modules, but has no hard dependency on it beyond the optional `skele_stk_setup`/`skele_stk_teardown` glue.
|
||||
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Platform-agnostic core** (`include/`) with OS/backend-specific implementations swapped in at build time (`src/platform/`)
|
||||
- **Two video backends** behind one API: software/SDL3 blit (`libskele`) and OpenGL (`libglskele`)
|
||||
- **Headless server builds**, linking only the engine core, no client/video/input code pulled in
|
||||
- **Fixed-timestep clock** with monotonic time and signal-aware sleep (POSIX and Win32)
|
||||
- **SDL3-backed input**: keyboard, gamepad (up to 4 pads), mouse, with held/pressed-this-frame state and analog axes
|
||||
- **256-color palette + software blit path** for indexed-color renderers (Doom-era style pipelines)
|
||||
- **Optional `stk` hot-reload integration** via `skele_stk_setup`/`skele_stk_teardown`
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Unix (Linux/BSD/macOS)
|
||||
./build.sh debug release
|
||||
|
||||
# Windows
|
||||
build.bat debug release
|
||||
```
|
||||
|
||||
Builds three static libraries into `bin/{debug,release}`:
|
||||
- `libskele.a` - software/SDL3 client backend
|
||||
- `libglskele.a` - OpenGL client backend
|
||||
- `libskeleserver.a` - headless engine core only
|
||||
|
||||
Raspberry Pi 5 is auto-detected and builds against GL 3.1 instead of the default 3.3.
|
||||
|
||||
### Installation
|
||||
|
||||
#### Unix (Linux/BSD/macOS)
|
||||
```bash
|
||||
./build.sh install
|
||||
```
|
||||
|
||||
Installs to `/usr` on Linux, `/usr/local` on BSD/macOS by default. Use `PREFIX` to customize:
|
||||
```bash
|
||||
./build.sh PREFIX=$HOME/.local install
|
||||
```
|
||||
|
||||
#### Windows
|
||||
```
|
||||
build.bat release
|
||||
```
|
||||
* Once finished building, copy the headers from `include/` to `your_project/include/skele/` and copy the built `.lib`/`.dll` files to your project's `lib` directory.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example (software client)
|
||||
|
||||
```c
|
||||
#include "client/blit.h"
|
||||
#include "client/input.h"
|
||||
#include "client/video.h"
|
||||
#include "clock.h"
|
||||
#include "skele.h"
|
||||
#include <stdlib.h>
|
||||
#include <stk/stk.h>
|
||||
|
||||
static uint8_t running = 1;
|
||||
|
||||
static void on_signal(void) { running = 0; }
|
||||
|
||||
int main(void)
|
||||
{
|
||||
skele_video_config_t video_cfg;
|
||||
uint64_t last, now, elapsed, accum = 0;
|
||||
uint8_t *pixels;
|
||||
|
||||
skele_clock_init(on_signal);
|
||||
|
||||
if (skele_stk_setup() != SKELE_INIT_SUCCESS)
|
||||
return 1;
|
||||
|
||||
if (skele_init() != SKELE_INIT_SUCCESS) {
|
||||
skele_stk_teardown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
video_cfg.render_width = SKELE_DEFAULT_RENDER_WIDTH;
|
||||
video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT;
|
||||
video_cfg.window_width = 0;
|
||||
video_cfg.window_height = 0;
|
||||
video_cfg.flags = 0;
|
||||
|
||||
if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) {
|
||||
skele_stk_teardown();
|
||||
skele_shutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
pixels = calloc(video_cfg.render_width * video_cfg.render_height,
|
||||
sizeof(uint8_t));
|
||||
|
||||
last = skele_time_ns();
|
||||
|
||||
while (running) {
|
||||
stk_poll();
|
||||
if (!skele_input_poll())
|
||||
break;
|
||||
|
||||
now = skele_time_ns();
|
||||
elapsed = now - last;
|
||||
accum += elapsed;
|
||||
last = now;
|
||||
|
||||
while (accum >= skele_tick_ns) {
|
||||
skele_tick();
|
||||
accum -= skele_tick_ns;
|
||||
}
|
||||
|
||||
skele_video_blit(pixels);
|
||||
skele_video_present();
|
||||
}
|
||||
|
||||
free(pixels);
|
||||
skele_video_shutdown();
|
||||
skele_stk_teardown();
|
||||
skele_shutdown();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
See `example/client`, `example/gl_client`, and `example/server` for the full reference loops, including a fixed-timestep accumulator suitable for interpolated rendering.
|
||||
|
||||
### Headless / Server Builds
|
||||
|
||||
Link `libskeleserver.a` instead of `libskele.a`/`libglskele.a` to get the engine core (clock, tick, `stk` glue) without pulling in any video/input/SDL3 dependency:
|
||||
|
||||
```bash
|
||||
cc -o my_server main.c -lskeleserver -lstk
|
||||
```
|
||||
|
||||
### API Reference
|
||||
|
||||
#### Core (`skele.h`)
|
||||
- `uint8_t skele_init(void)` / `void skele_shutdown(void)` - engine lifecycle
|
||||
- `void skele_tick(void)` - advance one simulation tick
|
||||
- `void skele_set_tick_rate(uint8_t rate)` - configure ticks/sec (default `SKELE_DEFAULT_TICK_RATE`)
|
||||
- `uint64_t skele_tick_ns` - nanoseconds per tick, for accumulator loops
|
||||
- `uint8_t skele_stk_setup(void)` / `void skele_stk_teardown(void)` - wire up `stk` hot-reload
|
||||
|
||||
#### Clock (`clock.h`)
|
||||
- `void skele_clock_init(void (*on_signal)(void))` - monotonic clock + signal handling
|
||||
- `uint64_t skele_time_ns(void)` - current monotonic time
|
||||
- `void skele_sleep_ns(uint64_t ns)` - sleep for a duration
|
||||
|
||||
#### Video (`client/video.h`)
|
||||
- `uint8_t skele_video_init(skele_video_config_t cfg)` / `void skele_video_shutdown(void)`
|
||||
- `void skele_video_present(void)` - present the current frame
|
||||
- `void skele_video_set_title(const char *title)`
|
||||
- `void skele_video_toggle_fullscreen(void)` / `skele_video_set_fullscreen_kind(...)`
|
||||
- `void skele_video_cycle_scale(void)` - cycle integer window scale
|
||||
- `void skele_video_set_mouse_grab(uint8_t grab)`
|
||||
|
||||
#### Blit / Palette (`client/blit.h`, `client/palette.h`)
|
||||
- `void skele_video_blit(uint8_t *pixels)` - blit an indexed-color framebuffer through the active palette
|
||||
- `void skele_palette_set(skele_palette_t pal)` / `skele_palette_set_index(uint8_t index, uint32_t color)`
|
||||
|
||||
#### Input (`client/input.h`)
|
||||
- `uint8_t skele_input_poll(void)` - pump platform events, returns `0` on quit
|
||||
- `uint8_t skele_key_down(skele_key_t key)` / `skele_key_held(skele_key_t key)` - pressed-this-frame vs. currently-held
|
||||
- `uint8_t skele_pad_connected(uint8_t pad)`, `skele_pad_button_down/held(...)`, `float skele_pad_axis(...)` - up to `SKELE_MAX_PADS` gamepads
|
||||
- `void skele_mouse_delta(int32_t *dx, int32_t *dy)`, `skele_mouse_button_down/held(...)`
|
||||
|
||||
---
|
||||
|
||||
## Project Status
|
||||
|
||||
**Current Version:** 0.0.0
|
||||
|
||||
Early/active development. Platform backends (clock, video, input) are functional and exercised by real client applications, but the engine core (`skele_init`/`skele_tick`) is still minimal. API is not yet stable; expect breaking changes until the core lifecycle is fleshed out.
|
||||
|
||||
No test suite yet.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Mozilla Public License 2.0 (MPL-2.0)
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions welcome! Please ensure code follows C99 standard and works across all supported platforms.
|
||||
|
||||
@@ -3,7 +3,7 @@ INPUT_SRC = src/platform/client/input/sdl.c
|
||||
|
||||
.include "config.mk"
|
||||
|
||||
ENGINE_SRCS = src/skele.c ${CLOCK_SRC}
|
||||
ENGINE_SRCS = src/skele.c src/fps.c ${CLOCK_SRC}
|
||||
SOFT_PLAT_SRCS = src/platform/client/video/sdl.c ${INPUT_SRC}
|
||||
GL_PLAT_SRCS = src/platform/client/video/gl.c ${INPUT_SRC}
|
||||
SOFT_CLIENT_SRCS = example/client/main.c
|
||||
|
||||
@@ -30,7 +30,8 @@ int main(int argc, char *argv[])
|
||||
video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT;
|
||||
video_cfg.window_width = 0;
|
||||
video_cfg.window_height = 0;
|
||||
video_cfg.flags = 0;
|
||||
video_cfg.max_fps = 0;
|
||||
video_cfg.flags = SKELE_VIDEO_VSYNC;
|
||||
|
||||
if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) {
|
||||
skele_stk_teardown();
|
||||
|
||||
@@ -29,7 +29,8 @@ int main(int argc, char *argv[])
|
||||
video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT;
|
||||
video_cfg.window_width = 0;
|
||||
video_cfg.window_height = 0;
|
||||
video_cfg.flags = 0;
|
||||
video_cfg.max_fps = 0;
|
||||
video_cfg.flags = SKELE_VIDEO_VSYNC;
|
||||
|
||||
if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) {
|
||||
skele_stk_teardown();
|
||||
|
||||
+18
-1
@@ -1,6 +1,7 @@
|
||||
#include "clock.h"
|
||||
#include "skele.h"
|
||||
#include <stk/stk.h>
|
||||
#include <stk/stk_log.h>
|
||||
|
||||
static uint8_t running = 1;
|
||||
|
||||
@@ -10,6 +11,7 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
uint64_t tick_start;
|
||||
uint64_t elapsed;
|
||||
uint64_t log_start;
|
||||
|
||||
skele_clock_init(on_signal);
|
||||
|
||||
@@ -21,6 +23,8 @@ int main(int argc, char *argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
log_start = skele_time_ns();
|
||||
|
||||
while (running) {
|
||||
tick_start = skele_time_ns();
|
||||
|
||||
@@ -28,8 +32,21 @@ int main(int argc, char *argv[])
|
||||
skele_tick();
|
||||
|
||||
elapsed = skele_time_ns() - tick_start;
|
||||
if (elapsed < skele_tick_ns)
|
||||
if (elapsed < skele_tick_ns) {
|
||||
skele_clock_idle_begin();
|
||||
skele_sleep_ns(skele_tick_ns - elapsed);
|
||||
skele_clock_idle_end();
|
||||
}
|
||||
|
||||
if (skele_time_ns() - log_start >= 1000000000ULL) {
|
||||
stk_log(STK_LOG_INFO,
|
||||
"tps=%.1f work=%.2fms idle=%.2fms total=%.2fms budget=%.2fms",
|
||||
skele_tps, skele_tick_work_time_ns / 1e6,
|
||||
skele_tick_idle_time_ns / 1e6,
|
||||
skele_tick_total_time_ns / 1e6,
|
||||
skele_tick_ns / 1e6);
|
||||
log_start = skele_time_ns();
|
||||
}
|
||||
}
|
||||
|
||||
skele_stk_teardown();
|
||||
|
||||
@@ -8,7 +8,7 @@ endif
|
||||
|
||||
include config.mk
|
||||
|
||||
ENGINE_SRCS := src/skele.c $(CLOCK_SRC)
|
||||
ENGINE_SRCS := src/skele.c src/fps.c $(CLOCK_SRC)
|
||||
SOFT_PLAT_SRCS := src/platform/client/video/sdl.c $(INPUT_SRC)
|
||||
GL_PLAT_SRCS := src/platform/client/video/gl.c $(INPUT_SRC)
|
||||
SOFT_CLIENT_SRCS := example/client/main.c
|
||||
|
||||
@@ -12,10 +12,14 @@ extern "C" {
|
||||
#define SKELE_VIDEO_RESIZABLE 0x04
|
||||
#define SKELE_VIDEO_HIGHDPI 0x08
|
||||
#define SKELE_VIDEO_FULLSCREEN_EXCLUSIVE 0x10
|
||||
#define SKELE_VIDEO_VSYNC 0x20
|
||||
|
||||
#define SKELE_DEFAULT_RENDER_WIDTH 320
|
||||
#define SKELE_DEFAULT_RENDER_HEIGHT 200
|
||||
|
||||
#define SKELE_VIDEO_FPS_UNCAPPED UINT16_MAX
|
||||
#define SKELE_VIDEO_FPS_FALLBACK 60
|
||||
|
||||
typedef enum {
|
||||
SKELE_FULLSCREEN_BORDERLESS,
|
||||
SKELE_FULLSCREEN_EXCLUSIVE
|
||||
@@ -26,12 +30,16 @@ typedef struct {
|
||||
uint16_t render_height;
|
||||
uint16_t window_width;
|
||||
uint16_t window_height;
|
||||
uint16_t max_fps;
|
||||
uint8_t flags;
|
||||
} skele_video_config_t;
|
||||
|
||||
uint8_t skele_video_init(skele_video_config_t cfg);
|
||||
void skele_video_shutdown(void);
|
||||
void skele_video_present(void);
|
||||
uint8_t skele_video_using_vsync(void);
|
||||
void skele_video_set_vsync(uint8_t enabled);
|
||||
void skele_video_set_max_fps(uint16_t max_fps);
|
||||
void skele_video_set_title(const char *title);
|
||||
void skele_video_toggle_fullscreen(void);
|
||||
void skele_video_set_fullscreen_kind(skele_fullscreen_kind_t kind);
|
||||
|
||||
@@ -7,9 +7,18 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern float skele_tps;
|
||||
extern uint64_t skele_tick_work_time_ns;
|
||||
extern uint64_t skele_tick_idle_time_ns;
|
||||
extern uint64_t skele_tick_total_time_ns;
|
||||
|
||||
void skele_clock_init(void (*on_signal)(void));
|
||||
uint64_t skele_time_ns(void);
|
||||
void skele_sleep_ns(uint64_t ns);
|
||||
void skele_clock_tick_begin(void);
|
||||
void skele_clock_tick_end(void);
|
||||
void skele_clock_idle_begin(void);
|
||||
void skele_clock_idle_end(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef SKELE_FPS_H
|
||||
#define SKELE_FPS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern float skele_fps;
|
||||
extern float skele_frame_work_time_ms;
|
||||
extern float skele_frame_render_time_ms;
|
||||
extern float skele_frame_budget_ms;
|
||||
|
||||
void skele_fps_init(void);
|
||||
void skele_fps_work_mark(void);
|
||||
void skele_fps_frame_mark(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SKELE_FPS_H */
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "fps.h"
|
||||
#include "clock.h"
|
||||
|
||||
float skele_fps = 0.0f;
|
||||
float skele_frame_render_time_ms = 0.0f;
|
||||
float skele_frame_budget_ms = 0.0f;
|
||||
float skele_frame_work_time_ms = 0.0f;
|
||||
|
||||
static uint64_t win_start = 0;
|
||||
static uint64_t last_frame_ns = 0;
|
||||
static uint64_t frame_ns_accum = 0;
|
||||
static uint64_t work_ns_accum = 0;
|
||||
static uint32_t frame_count = 0;
|
||||
|
||||
void skele_fps_init(void)
|
||||
{
|
||||
win_start = skele_time_ns();
|
||||
last_frame_ns = win_start;
|
||||
frame_ns_accum = 0;
|
||||
work_ns_accum = 0;
|
||||
frame_count = 0;
|
||||
skele_fps = 0.0f;
|
||||
skele_frame_render_time_ms = 0.0f;
|
||||
skele_frame_work_time_ms = 0.0f;
|
||||
}
|
||||
|
||||
void skele_fps_work_mark(void)
|
||||
{
|
||||
work_ns_accum += skele_time_ns() - last_frame_ns;
|
||||
}
|
||||
|
||||
void skele_fps_frame_mark(void)
|
||||
{
|
||||
uint64_t now = skele_time_ns();
|
||||
|
||||
frame_ns_accum += now - last_frame_ns;
|
||||
last_frame_ns = now;
|
||||
frame_count++;
|
||||
|
||||
if (now - win_start >= 1000000000ULL) {
|
||||
double elapsed_s = (double)(now - win_start) / 1e9;
|
||||
skele_fps = (float)(frame_count / elapsed_s);
|
||||
skele_frame_render_time_ms =
|
||||
(float)((frame_ns_accum / (double)frame_count) / 1e6);
|
||||
skele_frame_work_time_ms =
|
||||
(float)((work_ns_accum / (double)frame_count) / 1e6);
|
||||
|
||||
win_start = now;
|
||||
frame_ns_accum = 0;
|
||||
work_ns_accum = 0;
|
||||
frame_count = 0;
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,16 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint8_t key_held[SKELE_KEY_COUNT];
|
||||
static uint8_t key_down[SKELE_KEY_COUNT];
|
||||
static uint64_t key_held = 0;
|
||||
static uint64_t key_down = 0;
|
||||
|
||||
static SDL_Gamepad *pads[SKELE_MAX_PADS];
|
||||
static uint8_t pad_btn_held[SKELE_MAX_PADS][SKELE_PAD_BUTTON_COUNT];
|
||||
static uint8_t pad_btn_down[SKELE_MAX_PADS][SKELE_PAD_BUTTON_COUNT];
|
||||
static uint16_t pad_btn_held[SKELE_MAX_PADS];
|
||||
static uint16_t pad_btn_down[SKELE_MAX_PADS];
|
||||
static float pad_axes[SKELE_MAX_PADS][SKELE_PAD_AXIS_COUNT];
|
||||
|
||||
static uint8_t mouse_btn_held[SKELE_MOUSE_BUTTON_COUNT];
|
||||
static uint8_t mouse_btn_down[SKELE_MOUSE_BUTTON_COUNT];
|
||||
static uint8_t mouse_btn_held = 0;
|
||||
static uint8_t mouse_btn_down = 0;
|
||||
static int32_t mouse_dx = 0;
|
||||
static int32_t mouse_dy = 0;
|
||||
|
||||
@@ -249,9 +249,9 @@ uint8_t skele_input_poll(void)
|
||||
skele_pad_button_t btn;
|
||||
skele_mouse_button_t mbtn;
|
||||
|
||||
memset(key_down, 0, sizeof(key_down));
|
||||
key_down = 0;
|
||||
memset(pad_btn_down, 0, sizeof(pad_btn_down));
|
||||
memset(mouse_btn_down, 0, sizeof(mouse_btn_down));
|
||||
mouse_btn_down = 0;
|
||||
mouse_dx = 0;
|
||||
mouse_dy = 0;
|
||||
|
||||
@@ -268,15 +268,15 @@ uint8_t skele_input_poll(void)
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
mbtn = sdl_btn_to_mouse(e.button.button);
|
||||
if (mbtn != SKELE_MOUSE_BUTTON_COUNT) {
|
||||
mouse_btn_down[mbtn] = 1;
|
||||
mouse_btn_held[mbtn] = 1;
|
||||
mouse_btn_down |= (uint8_t)(1u << mbtn);
|
||||
mouse_btn_held |= (uint8_t)(1u << mbtn);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
mbtn = sdl_btn_to_mouse(e.button.button);
|
||||
if (mbtn != SKELE_MOUSE_BUTTON_COUNT)
|
||||
mouse_btn_held[mbtn] = 0;
|
||||
mouse_btn_held &= (uint8_t)~(1u << mbtn);
|
||||
break;
|
||||
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
@@ -292,8 +292,8 @@ uint8_t skele_input_poll(void)
|
||||
default:
|
||||
key = scancode_to_key(e.key.scancode);
|
||||
if (key != SKELE_KEY_UNKNOWN) {
|
||||
key_down[key] = 1;
|
||||
key_held[key] = 1;
|
||||
key_down |= (uint64_t)1 << key;
|
||||
key_held |= (uint64_t)1 << key;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -302,7 +302,7 @@ uint8_t skele_input_poll(void)
|
||||
case SDL_EVENT_KEY_UP:
|
||||
key = scancode_to_key(e.key.scancode);
|
||||
if (key != SKELE_KEY_UNKNOWN)
|
||||
key_held[key] = 0;
|
||||
key_held &= ~((uint64_t)1 << key);
|
||||
break;
|
||||
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
@@ -310,8 +310,7 @@ uint8_t skele_input_poll(void)
|
||||
if (slot >= 0) {
|
||||
pads[slot] = SDL_OpenGamepad(e.gdevice.which);
|
||||
if (pads[slot])
|
||||
memset(pad_btn_held[slot], 0,
|
||||
sizeof(pad_btn_held[slot]));
|
||||
pad_btn_held[slot] = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -320,8 +319,7 @@ uint8_t skele_input_poll(void)
|
||||
if (slot >= 0) {
|
||||
SDL_CloseGamepad(pads[slot]);
|
||||
pads[slot] = NULL;
|
||||
memset(pad_btn_held[slot], 0,
|
||||
sizeof(pad_btn_held[slot]));
|
||||
pad_btn_held[slot] = 0;
|
||||
memset(pad_axes[slot], 0,
|
||||
sizeof(pad_axes[slot]));
|
||||
}
|
||||
@@ -332,8 +330,8 @@ uint8_t skele_input_poll(void)
|
||||
btn =
|
||||
sdl_btn_to_pad((SDL_GamepadButton)e.gbutton.button);
|
||||
if (slot >= 0 && btn != SKELE_PAD_BUTTON_COUNT) {
|
||||
pad_btn_down[slot][btn] = 1;
|
||||
pad_btn_held[slot][btn] = 1;
|
||||
pad_btn_down[slot] |= (uint16_t)(1u << btn);
|
||||
pad_btn_held[slot] |= (uint16_t)(1u << btn);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -342,7 +340,7 @@ uint8_t skele_input_poll(void)
|
||||
btn =
|
||||
sdl_btn_to_pad((SDL_GamepadButton)e.gbutton.button);
|
||||
if (slot >= 0 && btn != SKELE_PAD_BUTTON_COUNT)
|
||||
pad_btn_held[slot][btn] = 0;
|
||||
pad_btn_held[slot] &= (uint16_t)~(1u << btn);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -359,12 +357,12 @@ uint8_t skele_input_poll(void)
|
||||
|
||||
uint8_t skele_key_down(skele_key_t key)
|
||||
{
|
||||
return key < SKELE_KEY_COUNT ? key_down[key] : 0;
|
||||
return key < SKELE_KEY_COUNT ? (uint8_t)((key_down >> key) & 1) : 0;
|
||||
}
|
||||
|
||||
uint8_t skele_key_held(skele_key_t key)
|
||||
{
|
||||
return key < SKELE_KEY_COUNT ? key_held[key] : 0;
|
||||
return key < SKELE_KEY_COUNT ? (uint8_t)((key_held >> key) & 1) : 0;
|
||||
}
|
||||
|
||||
uint8_t skele_pad_connected(uint8_t pad)
|
||||
@@ -375,14 +373,14 @@ uint8_t skele_pad_connected(uint8_t pad)
|
||||
uint8_t skele_pad_button_down(uint8_t pad, skele_pad_button_t btn)
|
||||
{
|
||||
return (pad < SKELE_MAX_PADS && btn < SKELE_PAD_BUTTON_COUNT)
|
||||
? pad_btn_down[pad][btn]
|
||||
? (uint8_t)((pad_btn_down[pad] >> btn) & 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
uint8_t skele_pad_button_held(uint8_t pad, skele_pad_button_t btn)
|
||||
{
|
||||
return (pad < SKELE_MAX_PADS && btn < SKELE_PAD_BUTTON_COUNT)
|
||||
? pad_btn_held[pad][btn]
|
||||
? (uint8_t)((pad_btn_held[pad] >> btn) & 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
@@ -403,10 +401,14 @@ void skele_mouse_delta(int32_t *dx, int32_t *dy)
|
||||
|
||||
uint8_t skele_mouse_button_down(skele_mouse_button_t btn)
|
||||
{
|
||||
return btn < SKELE_MOUSE_BUTTON_COUNT ? mouse_btn_down[btn] : 0;
|
||||
return btn < SKELE_MOUSE_BUTTON_COUNT
|
||||
? (uint8_t)((mouse_btn_down >> btn) & 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
uint8_t skele_mouse_button_held(skele_mouse_button_t btn)
|
||||
{
|
||||
return btn < SKELE_MOUSE_BUTTON_COUNT ? mouse_btn_held[btn] : 0;
|
||||
return btn < SKELE_MOUSE_BUTTON_COUNT
|
||||
? (uint8_t)((mouse_btn_held >> btn) & 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "client/video.h"
|
||||
#include "clock.h"
|
||||
#include "fps.h"
|
||||
#include "skele.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stk/stk_log.h>
|
||||
@@ -12,6 +14,63 @@ static uint8_t is_fullscreen = 0;
|
||||
static skele_fullscreen_kind_t last_fullscreen_kind =
|
||||
SKELE_FULLSCREEN_EXCLUSIVE;
|
||||
|
||||
static void (*present_fn)(void);
|
||||
static uint64_t frame_budget_ns = 0;
|
||||
static uint64_t last_present_ns = 0;
|
||||
static uint8_t vsync_active = 0;
|
||||
static uint8_t want_vsync = 0;
|
||||
static uint16_t configured_max_fps = 0;
|
||||
|
||||
static void present(void) { SDL_GL_SwapWindow(window); }
|
||||
|
||||
static void present_capped(void)
|
||||
{
|
||||
uint64_t now, elapsed;
|
||||
|
||||
SDL_GL_SwapWindow(window);
|
||||
|
||||
now = skele_time_ns();
|
||||
elapsed = now - last_present_ns;
|
||||
if (elapsed < frame_budget_ns)
|
||||
skele_sleep_ns(frame_budget_ns - elapsed);
|
||||
last_present_ns = skele_time_ns();
|
||||
}
|
||||
|
||||
static void apply_present_mode(void)
|
||||
{
|
||||
const SDL_DisplayMode *refresh_mode;
|
||||
uint16_t cap;
|
||||
|
||||
if (want_vsync && SDL_GL_SetSwapInterval(1))
|
||||
vsync_active = 1;
|
||||
else {
|
||||
SDL_GL_SetSwapInterval(0);
|
||||
vsync_active = 0;
|
||||
}
|
||||
|
||||
cap = configured_max_fps;
|
||||
|
||||
if (cap == SKELE_VIDEO_FPS_UNCAPPED) {
|
||||
frame_budget_ns = 0;
|
||||
skele_frame_budget_ms = 0.0f;
|
||||
present_fn = present;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cap) {
|
||||
refresh_mode = SDL_GetCurrentDisplayMode(
|
||||
SDL_GetDisplayForWindow(window));
|
||||
cap = (refresh_mode && refresh_mode->refresh_rate > 0)
|
||||
? (uint16_t)refresh_mode->refresh_rate
|
||||
: SKELE_VIDEO_FPS_FALLBACK;
|
||||
}
|
||||
|
||||
frame_budget_ns = 1000000000ULL / cap;
|
||||
skele_frame_budget_ms = frame_budget_ns / 1e6f;
|
||||
last_present_ns = skele_time_ns();
|
||||
present_fn = vsync_active ? present : present_capped;
|
||||
}
|
||||
|
||||
static uint8_t max_scale(uint16_t rw, uint16_t rh)
|
||||
{
|
||||
SDL_DisplayID display;
|
||||
@@ -159,8 +218,12 @@ uint8_t skele_video_init(skele_video_config_t cfg)
|
||||
return SKELE_INIT_FAILURE;
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
want_vsync = (cfg.flags & SKELE_VIDEO_VSYNC) ? 1 : 0;
|
||||
configured_max_fps = cfg.max_fps;
|
||||
apply_present_mode();
|
||||
|
||||
SDL_ShowWindow(window);
|
||||
skele_fps_init();
|
||||
return SKELE_INIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -179,7 +242,25 @@ void skele_video_shutdown(void)
|
||||
is_fullscreen = 0;
|
||||
}
|
||||
|
||||
void skele_video_present(void) { SDL_GL_SwapWindow(window); }
|
||||
void skele_video_present(void)
|
||||
{
|
||||
skele_fps_work_mark();
|
||||
present_fn();
|
||||
skele_fps_frame_mark();
|
||||
}
|
||||
uint8_t skele_video_using_vsync(void) { return vsync_active; }
|
||||
|
||||
void skele_video_set_vsync(uint8_t enabled)
|
||||
{
|
||||
want_vsync = enabled;
|
||||
apply_present_mode();
|
||||
}
|
||||
|
||||
void skele_video_set_max_fps(uint16_t max_fps)
|
||||
{
|
||||
configured_max_fps = max_fps;
|
||||
apply_present_mode();
|
||||
}
|
||||
void skele_video_cycle_scale(void) { cycle_scale(); }
|
||||
|
||||
void skele_video_toggle_fullscreen(void)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "client/blit.h"
|
||||
#include "client/palette.h"
|
||||
#include "client/video.h"
|
||||
#include "clock.h"
|
||||
#include "fps.h"
|
||||
#include "skele.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdint.h>
|
||||
@@ -21,6 +23,63 @@ static uint8_t is_fullscreen = 0;
|
||||
static skele_fullscreen_kind_t last_fullscreen_kind =
|
||||
SKELE_FULLSCREEN_EXCLUSIVE;
|
||||
|
||||
static void (*present_fn)(void);
|
||||
static uint64_t frame_budget_ns = 0;
|
||||
static uint64_t last_present_ns = 0;
|
||||
static uint8_t vsync_active = 0;
|
||||
static uint8_t want_vsync = 0;
|
||||
static uint16_t configured_max_fps = 0;
|
||||
|
||||
static void present(void) { SDL_RenderPresent(renderer); }
|
||||
|
||||
static void present_capped(void)
|
||||
{
|
||||
uint64_t now, elapsed;
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
|
||||
now = skele_time_ns();
|
||||
elapsed = now - last_present_ns;
|
||||
if (elapsed < frame_budget_ns)
|
||||
skele_sleep_ns(frame_budget_ns - elapsed);
|
||||
last_present_ns = skele_time_ns();
|
||||
}
|
||||
|
||||
static void apply_present_mode(void)
|
||||
{
|
||||
const SDL_DisplayMode *refresh_mode;
|
||||
uint16_t cap;
|
||||
|
||||
if (want_vsync && SDL_SetRenderVSync(renderer, 1))
|
||||
vsync_active = 1;
|
||||
else {
|
||||
SDL_SetRenderVSync(renderer, 0);
|
||||
vsync_active = 0;
|
||||
}
|
||||
|
||||
cap = configured_max_fps;
|
||||
|
||||
if (cap == SKELE_VIDEO_FPS_UNCAPPED) {
|
||||
frame_budget_ns = 0;
|
||||
skele_frame_budget_ms = 0.0f;
|
||||
present_fn = present;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cap) {
|
||||
refresh_mode = SDL_GetCurrentDisplayMode(
|
||||
SDL_GetDisplayForWindow(window));
|
||||
cap = (refresh_mode && refresh_mode->refresh_rate > 0)
|
||||
? (uint16_t)refresh_mode->refresh_rate
|
||||
: SKELE_VIDEO_FPS_FALLBACK;
|
||||
}
|
||||
|
||||
frame_budget_ns = 1000000000ULL / cap;
|
||||
skele_frame_budget_ms = frame_budget_ns / 1e6f;
|
||||
last_present_ns = skele_time_ns();
|
||||
present_fn = vsync_active ? present : present_capped;
|
||||
}
|
||||
|
||||
static uint8_t max_scale(uint16_t rw, uint16_t rh)
|
||||
{
|
||||
SDL_DisplayID display;
|
||||
@@ -82,7 +141,7 @@ uint8_t skele_video_init(skele_video_config_t cfg)
|
||||
SDL_DisplayID display;
|
||||
SDL_WindowFlags flags = 0;
|
||||
const SDL_DisplayMode *mode;
|
||||
uint16_t window_w, window_h, aw, ah;
|
||||
uint16_t window_w, window_h;
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) {
|
||||
stk_log(STK_LOG_ERROR, "video: SDL_Init failed: %s",
|
||||
@@ -125,24 +184,17 @@ uint8_t skele_video_init(skele_video_config_t cfg)
|
||||
return SKELE_INIT_FAILURE;
|
||||
}
|
||||
|
||||
aw = (uint16_t)((mode->w * 3) / 4);
|
||||
ah = (uint16_t)((mode->h * 3) / 4);
|
||||
cur_scale = 1;
|
||||
while ((vid_w * (cur_scale + 1)) <= aw &&
|
||||
(vid_h * (cur_scale + 1)) <= ah)
|
||||
cur_scale++;
|
||||
|
||||
cur_scale = max_scale(vid_w, vid_h);
|
||||
window_w = vid_w * cur_scale;
|
||||
window_h = vid_h * cur_scale;
|
||||
}
|
||||
|
||||
rgba_buf = malloc(vid_total * sizeof(uint32_t));
|
||||
rgba_buf = calloc(vid_total, sizeof(uint32_t));
|
||||
if (!rgba_buf) {
|
||||
stk_log(STK_LOG_ERROR, "video: out of memory");
|
||||
SDL_Quit();
|
||||
return SKELE_INIT_FAILURE;
|
||||
}
|
||||
memset(rgba_buf, 0, vid_total * sizeof(uint32_t));
|
||||
|
||||
window = SDL_CreateWindow("skele", window_w, window_h,
|
||||
flags | SDL_WINDOW_HIDDEN);
|
||||
@@ -188,9 +240,14 @@ uint8_t skele_video_init(skele_video_config_t cfg)
|
||||
}
|
||||
|
||||
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
|
||||
SDL_SetRenderVSync(renderer, 1);
|
||||
|
||||
want_vsync = (cfg.flags & SKELE_VIDEO_VSYNC) ? 1 : 0;
|
||||
configured_max_fps = cfg.max_fps;
|
||||
apply_present_mode();
|
||||
|
||||
memset(palette, 0, sizeof(palette));
|
||||
SDL_ShowWindow(window);
|
||||
skele_fps_init();
|
||||
return SKELE_INIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -230,7 +287,25 @@ void skele_video_blit(uint8_t *pixels)
|
||||
SDL_RenderTexture(renderer, texture, NULL, NULL);
|
||||
}
|
||||
|
||||
void skele_video_present(void) { SDL_RenderPresent(renderer); }
|
||||
void skele_video_present(void)
|
||||
{
|
||||
skele_fps_work_mark();
|
||||
present_fn();
|
||||
skele_fps_frame_mark();
|
||||
}
|
||||
uint8_t skele_video_using_vsync(void) { return vsync_active; }
|
||||
|
||||
void skele_video_set_vsync(uint8_t enabled)
|
||||
{
|
||||
want_vsync = enabled;
|
||||
apply_present_mode();
|
||||
}
|
||||
|
||||
void skele_video_set_max_fps(uint16_t max_fps)
|
||||
{
|
||||
configured_max_fps = max_fps;
|
||||
apply_present_mode();
|
||||
}
|
||||
void skele_video_cycle_scale(void) { cycle_scale(); }
|
||||
|
||||
void skele_video_toggle_fullscreen(void)
|
||||
|
||||
+61
-2
@@ -1,21 +1,80 @@
|
||||
#include "skele.h"
|
||||
#include "clock.h"
|
||||
#include <stk/stk.h>
|
||||
#include <stk/stk_log.h>
|
||||
|
||||
uint64_t skele_tick_ns = 1000000000ULL / SKELE_DEFAULT_TICK_RATE;
|
||||
|
||||
float skele_tps = 0.0f;
|
||||
uint64_t skele_tick_work_time_ns = 0;
|
||||
uint64_t skele_tick_idle_time_ns = 0;
|
||||
uint64_t skele_tick_total_time_ns = 0;
|
||||
|
||||
static uint8_t stk_initialized = 0;
|
||||
|
||||
static uint64_t tick_win_start = 0;
|
||||
static uint64_t tick_ns_accum = 0;
|
||||
static uint64_t tick_idle_accum = 0;
|
||||
static uint32_t tick_count = 0;
|
||||
static uint64_t tick_start_ns = 0;
|
||||
static uint64_t idle_start_ns = 0;
|
||||
|
||||
void skele_set_tick_rate(uint8_t rate)
|
||||
{
|
||||
skele_tick_ns = 1000000000ULL / (rate ? rate : SKELE_DEFAULT_TICK_RATE);
|
||||
}
|
||||
|
||||
uint8_t skele_init(void) { return SKELE_INIT_SUCCESS; }
|
||||
uint8_t skele_init(void)
|
||||
{
|
||||
tick_win_start = skele_time_ns();
|
||||
tick_ns_accum = 0;
|
||||
tick_idle_accum = 0;
|
||||
tick_count = 0;
|
||||
skele_tps = 0.0f;
|
||||
skele_tick_work_time_ns = 0;
|
||||
skele_tick_idle_time_ns = 0;
|
||||
skele_tick_total_time_ns = 0;
|
||||
return SKELE_INIT_SUCCESS;
|
||||
}
|
||||
|
||||
void skele_shutdown(void) {}
|
||||
|
||||
void skele_tick(void) {}
|
||||
void skele_tick(void)
|
||||
{
|
||||
skele_clock_tick_begin();
|
||||
skele_clock_tick_end();
|
||||
}
|
||||
|
||||
void skele_clock_tick_begin(void) { tick_start_ns = skele_time_ns(); }
|
||||
|
||||
void skele_clock_tick_end(void)
|
||||
{
|
||||
uint64_t now = skele_time_ns();
|
||||
|
||||
tick_ns_accum += now - tick_start_ns;
|
||||
tick_count++;
|
||||
|
||||
if (now - tick_win_start >= 1000000000ULL) {
|
||||
double elapsed_s = (now - tick_win_start) / 1e9;
|
||||
skele_tps = tick_count / elapsed_s;
|
||||
skele_tick_work_time_ns = tick_ns_accum / tick_count;
|
||||
skele_tick_idle_time_ns = tick_idle_accum / tick_count;
|
||||
skele_tick_total_time_ns =
|
||||
skele_tick_work_time_ns + skele_tick_idle_time_ns;
|
||||
|
||||
tick_win_start = now;
|
||||
tick_ns_accum = 0;
|
||||
tick_idle_accum = 0;
|
||||
tick_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void skele_clock_idle_begin(void) { idle_start_ns = skele_time_ns(); }
|
||||
|
||||
void skele_clock_idle_end(void)
|
||||
{
|
||||
tick_idle_accum += skele_time_ns() - idle_start_ns;
|
||||
}
|
||||
|
||||
uint8_t skele_stk_setup(void)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user