mirror of
https://github.com/izzy2lost/wipeout-rewrite.git
synced 2026-07-06 00:20:05 -07:00
Initial
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
/wipeout.sublime-project
|
||||
/wipeout.sublime-workspace
|
||||
|
||||
/wipeout/
|
||||
/build/
|
||||
|
||||
/wipegame
|
||||
/save.dat
|
||||
.DS_STORE
|
||||
@@ -0,0 +1,166 @@
|
||||
CC ?= gcc
|
||||
EMCC ?= emcc
|
||||
UNAME_S := $(shell uname -s)
|
||||
RENDERER ?= GL
|
||||
DEBUG ?= false
|
||||
|
||||
L_FLAGS ?= -lm -rdynamic
|
||||
C_FLAGS ?= -std=gnu99 -Wall -Wno-unused-variable
|
||||
|
||||
ifeq ($(DEBUG), true)
|
||||
C_FLAGS := $(C_FLAGS) -g
|
||||
else
|
||||
C_FLAGS := $(C_FLAGS) -O3
|
||||
endif
|
||||
|
||||
|
||||
# Rendeder ---------------------------------------------------------------------
|
||||
|
||||
ifeq ($(RENDERER), GL)
|
||||
RENDERER_SRC = src/render_gl.c
|
||||
C_FLAGS := $(C_FLAGS) -DRENDERER_GL
|
||||
else
|
||||
$(error Unknown RENDERER)
|
||||
endif
|
||||
|
||||
|
||||
|
||||
|
||||
# macOS ------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
C_FLAGS := $(C_FLAGS) -x objective-c -I/opt/homebrew/include -D_THREAD_SAFE -w
|
||||
L_FLAGS := $(L_FLAGS) -L/opt/homebrew/lib -framework Foundation
|
||||
|
||||
ifeq ($(RENDERER), GL)
|
||||
L_FLAGS := $(L_FLAGS) -lGLEW -GLU -framework OpenGL
|
||||
endif
|
||||
|
||||
L_FLAGS_SDL = -lSDL2
|
||||
L_FLAGS_SOKOL = -framework Cocoa -framework QuartzCore -framework AudioToolbox
|
||||
|
||||
|
||||
# Linux ------------------------------------------------------------------------
|
||||
|
||||
else ifeq ($(UNAME_S), Linux)
|
||||
ifeq ($(RENDERER), GL)
|
||||
L_FLAGS := $(L_FLAGS) -lGLEW -lGL
|
||||
endif
|
||||
|
||||
L_FLAGS_SDL = -lSDL2
|
||||
L_FLAGS_SOKOL = -lX11 -lXcursor -pthread -lXi -ldl -lasound
|
||||
|
||||
|
||||
# Windows ----------------------------------------------------------------------
|
||||
|
||||
else ifeq ($(OS), Windows_NT)
|
||||
$(error TODO: FLAGS for windows have not been set up. Please modify this makefile and send a PR!)
|
||||
|
||||
|
||||
|
||||
else
|
||||
$(error Unknown environment)
|
||||
endif
|
||||
|
||||
|
||||
|
||||
# Source files -----------------------------------------------------------------
|
||||
|
||||
TARGET_NATIVE ?= wipegame
|
||||
BUILD_DIR = build/obj/native
|
||||
BUILD_DIR_WASM = build/obj/wasm
|
||||
|
||||
WASM_RELEASE_DIR ?= build/wasm
|
||||
TARGET_WASM ?= $(WASM_RELEASE_DIR)/wipeout.js
|
||||
TARGET_WASM_MINIMAL ?= $(WASM_RELEASE_DIR)/wipeout-minimal.js
|
||||
|
||||
COMMON_SRC = \
|
||||
src/wipeout/race.c \
|
||||
src/wipeout/camera.c \
|
||||
src/wipeout/object.c \
|
||||
src/wipeout/droid.c \
|
||||
src/wipeout/ui.c \
|
||||
src/wipeout/hud.c \
|
||||
src/wipeout/image.c \
|
||||
src/wipeout/game.c \
|
||||
src/wipeout/menu.c \
|
||||
src/wipeout/main_menu.c \
|
||||
src/wipeout/ingame_menus.c \
|
||||
src/wipeout/title.c \
|
||||
src/wipeout/intro.c \
|
||||
src/wipeout/scene.c \
|
||||
src/wipeout/ship.c \
|
||||
src/wipeout/ship_ai.c \
|
||||
src/wipeout/ship_player.c \
|
||||
src/wipeout/track.c \
|
||||
src/wipeout/weapon.c \
|
||||
src/wipeout/particle.c \
|
||||
src/wipeout/sfx.c \
|
||||
src/utils.c \
|
||||
src/types.c \
|
||||
src/system.c \
|
||||
src/mem.c \
|
||||
src/input.c \
|
||||
$(RENDERER_SRC)
|
||||
|
||||
|
||||
# Targets native ---------------------------------------------------------------
|
||||
|
||||
COMMON_OBJ = $(patsubst %.c, $(BUILD_DIR)/%.o, $(COMMON_SRC))
|
||||
|
||||
sdl: $(BUILD_DIR)/src/platform_sdl.o
|
||||
sdl: $(COMMON_OBJ)
|
||||
$(CC) $^ -o $(TARGET_NATIVE) $(L_FLAGS) $(L_FLAGS_SDL)
|
||||
|
||||
sokol: $(BUILD_DIR)/src/platform_sokol.o
|
||||
sokol: $(COMMON_OBJ)
|
||||
$(CC) $^ -o $(TARGET_NATIVE) $(L_FLAGS) $(L_FLAGS_SOKOL)
|
||||
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
$(BUILD_DIR)/%.o: %.c
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) $(C_FLAGS) -c $< -o $@
|
||||
|
||||
|
||||
# Targets wasm -----------------------------------------------------------------
|
||||
|
||||
COMMON_OBJ_WASM = $(patsubst %.c, $(BUILD_DIR_WASM)/%.o, $(COMMON_SRC))
|
||||
wasm: wasm_full wasm_minimal
|
||||
cp src/wasm-index.html $(WASM_RELEASE_DIR)/game.html
|
||||
|
||||
|
||||
wasm_full: $(BUILD_DIR_WASM)/src/platform_sokol.o
|
||||
wasm_full: $(COMMON_OBJ_WASM)
|
||||
mkdir -p $(WASM_RELEASE_DIR)
|
||||
$(EMCC) $^ -o $(TARGET_WASM) -lGLEW -lGL \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s ENVIRONMENT=web \
|
||||
--preload-file wipeout
|
||||
|
||||
wasm_minimal: $(BUILD_DIR_WASM)/src/platform_sokol.o
|
||||
wasm_minimal: $(COMMON_OBJ_WASM)
|
||||
mkdir -p $(WASM_RELEASE_DIR)
|
||||
$(EMCC) $^ -o $(TARGET_WASM_MINIMAL) -lGLEW -lGL \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s ENVIRONMENT=web \
|
||||
--preload-file wipeout \
|
||||
--exclude-file wipeout/music \
|
||||
--exclude-file wipeout/intro.mpeg
|
||||
|
||||
$(BUILD_DIR_WASM):
|
||||
mkdir -p $(BUILD_DIR_WASM)
|
||||
|
||||
$(BUILD_DIR_WASM)/%.o: %.c
|
||||
mkdir -p $(dir $@)
|
||||
$(EMCC) $(C_FLAGS) -c $< -o $@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(RM) -rf $(BUILD_DIR) $(BUILD_DIR_WASM) $(WASM_RELEASE_DIR)
|
||||
@@ -0,0 +1,126 @@
|
||||
# wipEout Rewrite
|
||||
|
||||
This is a re-implementation of the 1995 PSX game wipEout.
|
||||
|
||||
Play here: https://phoboslab.org/wipegame/
|
||||
|
||||
More info in my blog: https://phoboslab.org/log/2023/08/rewriting-wipeout
|
||||
|
||||
|
||||
⚠️ Work in progress. Expect bugs.
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
The game currently supports two different platform-backends: [SDL2](https://github.com/libsdl-org/SDL) and [Sokol](https://github.com/floooh/sokol). The only difference in features is that the SDL2 backend supports game controllers (joysticks, gamepads), while the Sokol backend does not.
|
||||
|
||||
|
||||
### Linux
|
||||
|
||||
```
|
||||
# for SDL2 backend
|
||||
apt install libsdl2-dev
|
||||
make sdl
|
||||
```
|
||||
|
||||
```
|
||||
# for Sokol backend
|
||||
apt install libx11-dev libxcursor-dev libxi-dev libasound2-dev
|
||||
make sokol
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
Currently only the SDL2 backend works. macOS is very picky about the GLSL shader version when compiling with Sokol and OpenGL3.3; it shouldn't be too difficult to get it working, but will probably require a bunch of `#ifdefs` for SDL and WASM. PRs welcome!
|
||||
|
||||
```
|
||||
brew install sdl2
|
||||
make sdl
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
In theory both backends should work on Windows, but the Makefile is missing the proper compiler flags. Please send a PR!
|
||||
|
||||
_todo_
|
||||
|
||||
|
||||
### WASM
|
||||
|
||||
Install [emscripten](https://emscripten.org/) and activate emsdk, so that `emcc` is in your `PATH`. The WASM version automatically
|
||||
selects the Sokol backend. I'm not sure what needs to be done to make the SDL2 backend work with WASM.
|
||||
|
||||
```
|
||||
make wasm
|
||||
```
|
||||
|
||||
This builds the minimal version (no music, no intro) as well as the full version.
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
This repository does not contain the assets (textures, 3d models etc.) required to run the game. This code mostly assumes to have the PSX NTSC data, but some menu models from the PC version are loaded as well. Both of these can be easily found on archive.org and similar sites. The music (optional) needs to be provided in [QOA format](https://github.com/phoboslab/qoa). The intro video as MPEG1.
|
||||
|
||||
The directory structure is assumed to be as follows
|
||||
|
||||
```
|
||||
./wipegame # the executable
|
||||
./wipeout/textures/
|
||||
./wipeout/music/track01.qoa
|
||||
./wipeout/music/track02.qoa
|
||||
...
|
||||
```
|
||||
|
||||
Note that the blog post announcing this project may or may not provide a link to a ZIP containing all files needed. Who knows!
|
||||
|
||||
|
||||
|
||||
## Ideas for improvements
|
||||
|
||||
PRs Welcome.
|
||||
|
||||
### Not yet implemented
|
||||
|
||||
Some things from the original game are not yet implemented in this rewrite. This includes
|
||||
|
||||
- screen shake effect
|
||||
- game-end animations, formerly `Spline.cpp` (the end messages are just shown over the attract mode cameras)
|
||||
- viewing highscores in options menu
|
||||
- controller options menu
|
||||
- reverb for sfx and music when there's more than 4 track faces (tunnels and such)
|
||||
- some more? grep the source for `TODO` and `FIXME`
|
||||
|
||||
### Gameplay, Visuals
|
||||
|
||||
- less punishing physics for ship vs. ship collisions
|
||||
- less punishing physics for sideways ship vs. track collisions (i.e. wall grinding like in newer wipEouts)
|
||||
- somehow resolve the issue of inevitably running into an enemy that you just shot
|
||||
- add option to lessen the roll in the internal view
|
||||
- add additional external view that behaves more like in modern racing games
|
||||
- dynamic lighting on ships
|
||||
- allow lower resolutions and a drawing mode that resembles the PSX original
|
||||
- the scene geometry could use some touch-ups to make an infinite draw distance option less awkward
|
||||
- increase FOV when going over a boost
|
||||
- better menu models for game exit and video options
|
||||
- gamepad analog input feels like balancing an egg
|
||||
- fix collision issues on junctions (also present in the original)
|
||||
|
||||
### Technical
|
||||
|
||||
- implement frustum culling for scene geometry, the track and ships. Currently everything within the fadeout radius is drawn.
|
||||
- put all static geometry into a GPU-side buffer. Currently all triangles are constructed at draw time. Uploading geometry is complicated a bit by the fact that some scene animations and the ship's exhaust need to update geometry for each frame.
|
||||
- the menu system is... not great. It's better than the 5000 lines of spaghetti that it was before, but the different layouts need a lot of `if`s
|
||||
- the save data is just dumping the whole struct on disk. A textual format would be preferable.
|
||||
- since this whole thing is relying on some custom assembled assets anyway, maybe all SFX should be in QOA format too (like the music). Or switch everything to Vorbis.
|
||||
- a lot of functions assume that there's just one player. This needs to be fixed for a potential splitscreen mode.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
There is none. This code may or may not be based on the source code of the PC (ATI-Rage) version that was leaked in 2022. If it were, it would probably violate copyright law, but it may also fall under fair use ¯\\\_(ツ)\_/¯
|
||||
|
||||
Working with this source code is probably fine, considering that this game was originally released 28 years ago (in 1995), that the current copyright holders historically didn't care about any wipEout related files or code being available on the net and that the game is currently not purchasable in any shape or form.
|
||||
|
||||
In any case, you may NOT use this source code in a commercial release. A commercial release includes hosting it on a website that shows any forms of advertising.
|
||||
|
||||
PS.: Hey Sony! If you're reading this, I would love to work on a proper, officially sanctioned remaster. Please get in touch <3
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "input.h"
|
||||
#include "utils.h"
|
||||
|
||||
static const char *button_names[] = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
[INPUT_KEY_A] = "a",
|
||||
[INPUT_KEY_B] = "b",
|
||||
[INPUT_KEY_C] = "c",
|
||||
[INPUT_KEY_D] = "d",
|
||||
[INPUT_KEY_E] = "e",
|
||||
[INPUT_KEY_F] = "f",
|
||||
[INPUT_KEY_G] = "g",
|
||||
[INPUT_KEY_H] = "h",
|
||||
[INPUT_KEY_I] = "i",
|
||||
[INPUT_KEY_J] = "j",
|
||||
[INPUT_KEY_K] = "k",
|
||||
[INPUT_KEY_L] = "l",
|
||||
[INPUT_KEY_M] = "m",
|
||||
[INPUT_KEY_N] = "n",
|
||||
[INPUT_KEY_O] = "o",
|
||||
[INPUT_KEY_P] = "p",
|
||||
[INPUT_KEY_Q] = "q",
|
||||
[INPUT_KEY_R] = "r",
|
||||
[INPUT_KEY_S] = "s",
|
||||
[INPUT_KEY_T] = "t",
|
||||
[INPUT_KEY_U] = "u",
|
||||
[INPUT_KEY_V] = "v",
|
||||
[INPUT_KEY_W] = "w",
|
||||
[INPUT_KEY_X] = "x",
|
||||
[INPUT_KEY_Y] = "y",
|
||||
[INPUT_KEY_Z] = "z",
|
||||
[INPUT_KEY_1] = "1",
|
||||
[INPUT_KEY_2] = "2",
|
||||
[INPUT_KEY_3] = "3",
|
||||
[INPUT_KEY_4] = "4",
|
||||
[INPUT_KEY_5] = "5",
|
||||
[INPUT_KEY_6] = "6",
|
||||
[INPUT_KEY_7] = "7",
|
||||
[INPUT_KEY_8] = "8",
|
||||
[INPUT_KEY_9] = "9",
|
||||
[INPUT_KEY_0] = "0",
|
||||
[INPUT_KEY_RETURN] = "return",
|
||||
[INPUT_KEY_ESCAPE] = "escape",
|
||||
[INPUT_KEY_BACKSPACE] = "backspace",
|
||||
[INPUT_KEY_TAB] = "tab",
|
||||
[INPUT_KEY_SPACE] = "space",
|
||||
[INPUT_KEY_MINUS] = "minus",
|
||||
[INPUT_KEY_EQUALS] = "equals",
|
||||
[INPUT_KEY_LEFTBRACKET] = "left_bracket",
|
||||
[INPUT_KEY_RIGHTBRACKET] = "right_bracket",
|
||||
[INPUT_KEY_BACKSLASH] = "backslash",
|
||||
[INPUT_KEY_HASH] = "hash",
|
||||
[INPUT_KEY_SEMICOLON] = "semicolon",
|
||||
[INPUT_KEY_APOSTROPHE] = "apostrophe",
|
||||
[INPUT_KEY_TILDE] = "tilde",
|
||||
[INPUT_KEY_COMMA] = "comma",
|
||||
[INPUT_KEY_PERIOD] = "period",
|
||||
[INPUT_KEY_SLASH] = "slash",
|
||||
[INPUT_KEY_CAPSLOCK] = "capslock",
|
||||
[INPUT_KEY_F1] = "f1",
|
||||
[INPUT_KEY_F2] = "f2",
|
||||
[INPUT_KEY_F3] = "f3",
|
||||
[INPUT_KEY_F4] = "f4",
|
||||
[INPUT_KEY_F5] = "f5",
|
||||
[INPUT_KEY_F6] = "f6",
|
||||
[INPUT_KEY_F7] = "f7",
|
||||
[INPUT_KEY_F8] = "f8",
|
||||
[INPUT_KEY_F9] = "f9",
|
||||
[INPUT_KEY_F10] = "f10",
|
||||
[INPUT_KEY_F11] = "f11",
|
||||
[INPUT_KEY_F12] = "f12",
|
||||
[INPUT_KEY_PRINTSCREEN] = "print_screen",
|
||||
[INPUT_KEY_SCROLLLOCK] = "scroll_lock",
|
||||
[INPUT_KEY_PAUSE] = "pause",
|
||||
[INPUT_KEY_INSERT] = "insert",
|
||||
[INPUT_KEY_HOME] = "home",
|
||||
[INPUT_KEY_PAGEUP] = "page_up",
|
||||
[INPUT_KEY_DELETE] = "delete",
|
||||
[INPUT_KEY_END] = "end",
|
||||
[INPUT_KEY_PAGEDOWN] = "page_down",
|
||||
[INPUT_KEY_RIGHT] = "right",
|
||||
[INPUT_KEY_LEFT] = "left",
|
||||
[INPUT_KEY_DOWN] = "down",
|
||||
[INPUT_KEY_UP] = "up",
|
||||
[INPUT_KEY_NUMLOCK] = "num_lock",
|
||||
[INPUT_KEY_KP_DIVIDE] = "keypad_divide",
|
||||
[INPUT_KEY_KP_MULTIPLY] = "keypad_multiply",
|
||||
[INPUT_KEY_KP_MINUS] = "keypad_minus",
|
||||
[INPUT_KEY_KP_PLUS] = "keypad_plus",
|
||||
[INPUT_KEY_KP_ENTER] = "keypad_enter",
|
||||
[INPUT_KEY_KP_1] = "keypad_1",
|
||||
[INPUT_KEY_KP_2] = "keypad_2",
|
||||
[INPUT_KEY_KP_3] = "keypad_3",
|
||||
[INPUT_KEY_KP_4] = "keypad_4",
|
||||
[INPUT_KEY_KP_5] = "keypad_5",
|
||||
[INPUT_KEY_KP_6] = "keypad_6",
|
||||
[INPUT_KEY_KP_7] = "keypad_7",
|
||||
[INPUT_KEY_KP_8] = "keypad_8",
|
||||
[INPUT_KEY_KP_9] = "keypad_9",
|
||||
[INPUT_KEY_KP_0] = "keypad_0",
|
||||
[INPUT_KEY_KP_PERIOD] = "keypad_period",
|
||||
|
||||
[INPUT_KEY_LCTRL] = "left_ctrl",
|
||||
[INPUT_KEY_LSHIFT] = "left_shift",
|
||||
[INPUT_KEY_LALT] = "left_alt",
|
||||
[INPUT_KEY_LGUI] = "left_gui",
|
||||
[INPUT_KEY_RCTRL] = "right_ctrl",
|
||||
[INPUT_KEY_RSHIFT] = "right_shift",
|
||||
[INPUT_KEY_RALT] = "right_alt",
|
||||
NULL,
|
||||
[INPUT_GAMEPAD_A] = "gamepad_a",
|
||||
[INPUT_GAMEPAD_Y] = "gamepad_y",
|
||||
[INPUT_GAMEPAD_B] = "gamepad_b",
|
||||
[INPUT_GAMEPAD_X] = "gamepad_x",
|
||||
[INPUT_GAMEPAD_L_SHOULDER] = "gamepad_left_shoulder",
|
||||
[INPUT_GAMEPAD_R_SHOULDER] = "gamepad_right_shoulder",
|
||||
[INPUT_GAMEPAD_L_TRIGGER] = "gamepad_left_trigger",
|
||||
[INPUT_GAMEPAD_R_TRIGGER] = "gamepad_right_trigger",
|
||||
[INPUT_GAMEPAD_SELECT] = "gamepad_select",
|
||||
[INPUT_GAMEPAD_START] = "gamepad_start",
|
||||
[INPUT_GAMEPAD_L_STICK_PRESS] = "gamepad_left_stick_press",
|
||||
[INPUT_GAMEPAD_R_STICK_PRESS] = "gamepad_right_stick_press",
|
||||
[INPUT_GAMEPAD_DPAD_UP] = "gamepad_dpad_up",
|
||||
[INPUT_GAMEPAD_DPAD_DOWN] = "gamepad_dpad_down",
|
||||
[INPUT_GAMEPAD_DPAD_LEFT] = "gamepad_dpad_left",
|
||||
[INPUT_GAMEPAD_DPAD_RIGHT] = "gamepad_dpad_right",
|
||||
[INPUT_GAMEPAD_HOME] = "gamepad_home",
|
||||
[INPUT_GAMEPAD_L_STICK_UP] = "gamepad_left_stick_up",
|
||||
[INPUT_GAMEPAD_L_STICK_DOWN] = "gamepad_left_stick_down",
|
||||
[INPUT_GAMEPAD_L_STICK_LEFT] = "gamepad_left_stick_left",
|
||||
[INPUT_GAMEPAD_L_STICK_RIGHT] = "gamepad_left_stick_right",
|
||||
[INPUT_GAMEPAD_R_STICK_UP] = "gamepad_right_stick_up",
|
||||
[INPUT_GAMEPAD_R_STICK_DOWN] = "gamepad_right_stick_down",
|
||||
[INPUT_GAMEPAD_R_STICK_LEFT] = "gamepad_right_stick_left",
|
||||
[INPUT_GAMEPAD_R_STICK_RIGHT] = "gamepad_right_stick_right",
|
||||
NULL,
|
||||
[INPUT_MOUSE_LEFT] = "mouse_left",
|
||||
[INPUT_MOUSE_MIDDLE] = "mouse_middle",
|
||||
[INPUT_MOUSE_RIGHT] = "mouse_right",
|
||||
[INPUT_MOUSE_WHEEL_UP] = "mouse_wheel_up",
|
||||
[INPUT_MOUSE_WHEEL_DOWN] = "mouse_wheel_down",
|
||||
};
|
||||
|
||||
static float actions_state[INPUT_ACTION_MAX];
|
||||
static bool actions_pressed[INPUT_ACTION_MAX];
|
||||
static bool actions_released[INPUT_ACTION_MAX];
|
||||
|
||||
static uint8_t expected_button[INPUT_ACTION_MAX];
|
||||
static uint8_t bindings[INPUT_LAYER_MAX][INPUT_BUTTON_MAX];
|
||||
|
||||
static input_capture_callback_t capture_callback;
|
||||
static void *capture_user;
|
||||
|
||||
static int32_t mouse_x;
|
||||
static int32_t mouse_y;
|
||||
|
||||
void input_init() {
|
||||
input_unbind_all(INPUT_LAYER_SYSTEM);
|
||||
input_unbind_all(INPUT_LAYER_USER);
|
||||
}
|
||||
|
||||
void input_cleanup() {
|
||||
|
||||
}
|
||||
|
||||
void input_clear() {
|
||||
clear(actions_pressed);
|
||||
clear(actions_released);
|
||||
}
|
||||
|
||||
void input_set_layer_button_state(input_layer_t layer, button_t button, float state) {
|
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer);
|
||||
|
||||
uint8_t action = bindings[layer][button];
|
||||
if (action == INPUT_ACTION_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t expected = expected_button[action];
|
||||
if (!expected || expected == button) {
|
||||
state = (state > INPUT_DEADZONE) ? state : 0;
|
||||
|
||||
if (state && !actions_state[action]) {
|
||||
actions_pressed[action] = true;
|
||||
expected_button[action] = button;
|
||||
}
|
||||
else if (!state && actions_state[action]) {
|
||||
actions_released[action] = true;
|
||||
expected_button[action] = INPUT_BUTTON_NONE;
|
||||
}
|
||||
actions_state[action] = state;
|
||||
}
|
||||
}
|
||||
|
||||
void input_set_button_state(button_t button, float state) {
|
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button);
|
||||
|
||||
if (capture_callback) {
|
||||
if (state) {
|
||||
capture_callback(capture_user, button, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
input_set_layer_button_state(INPUT_LAYER_SYSTEM, button, state);
|
||||
input_set_layer_button_state(INPUT_LAYER_USER, button, state);
|
||||
}
|
||||
|
||||
void input_set_mouse_pos(int32_t x, int32_t y) {
|
||||
mouse_x = x;
|
||||
mouse_y = y;
|
||||
}
|
||||
|
||||
void input_capture(input_capture_callback_t cb, void *user) {
|
||||
capture_callback = cb;
|
||||
capture_user = user;
|
||||
clear(actions_state);
|
||||
}
|
||||
|
||||
void input_textinput(int32_t ascii_char) {
|
||||
if (capture_callback) {
|
||||
capture_callback(capture_user, 0, ascii_char);
|
||||
}
|
||||
}
|
||||
|
||||
void input_bind(input_layer_t layer, button_t button, uint8_t action) {
|
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button);
|
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action);
|
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer);
|
||||
|
||||
bindings[layer][button] = action;
|
||||
}
|
||||
|
||||
uint8_t input_bound_to_action(button_t button) {
|
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button);
|
||||
return bindings[INPUT_LAYER_USER][button];
|
||||
}
|
||||
|
||||
void input_unbind(input_layer_t layer, button_t button) {
|
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer);
|
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button);
|
||||
|
||||
bindings[layer][button] = INPUT_ACTION_NONE;
|
||||
}
|
||||
|
||||
void input_unbind_all(input_layer_t layer) {
|
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer);
|
||||
|
||||
for (uint32_t button = 0; button < INPUT_BUTTON_MAX; button++) {
|
||||
input_unbind(layer, button);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float input_state(uint8_t action) {
|
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action);
|
||||
return actions_state[action];
|
||||
}
|
||||
|
||||
|
||||
bool input_pressed(uint8_t action) {
|
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action);
|
||||
return actions_pressed[action];
|
||||
}
|
||||
|
||||
|
||||
bool input_released(uint8_t action) {
|
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action);
|
||||
return actions_released[action];
|
||||
}
|
||||
|
||||
vec2_t input_mouse_pos() {
|
||||
return vec2(mouse_x, mouse_y);
|
||||
}
|
||||
|
||||
|
||||
button_t input_name_to_button(const char *name) {
|
||||
for (int32_t i = 0; i < INPUT_BUTTON_MAX; i++) {
|
||||
if (button_names[i] && strcmp(name, button_names[i]) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INPUT_INVALID;
|
||||
}
|
||||
|
||||
const char *input_button_to_name(button_t button) {
|
||||
if (
|
||||
button < 0 || button >= INPUT_BUTTON_MAX ||
|
||||
!button_names[button]
|
||||
) {
|
||||
return NULL;
|
||||
}
|
||||
return button_names[button];
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
#ifndef INPUT_H
|
||||
#define INPUT_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef enum {
|
||||
INPUT_INVALID = 0,
|
||||
INPUT_KEY_A = 4,
|
||||
INPUT_KEY_B = 5,
|
||||
INPUT_KEY_C = 6,
|
||||
INPUT_KEY_D = 7,
|
||||
INPUT_KEY_E = 8,
|
||||
INPUT_KEY_F = 9,
|
||||
INPUT_KEY_G = 10,
|
||||
INPUT_KEY_H = 11,
|
||||
INPUT_KEY_I = 12,
|
||||
INPUT_KEY_J = 13,
|
||||
INPUT_KEY_K = 14,
|
||||
INPUT_KEY_L = 15,
|
||||
INPUT_KEY_M = 16,
|
||||
INPUT_KEY_N = 17,
|
||||
INPUT_KEY_O = 18,
|
||||
INPUT_KEY_P = 19,
|
||||
INPUT_KEY_Q = 20,
|
||||
INPUT_KEY_R = 21,
|
||||
INPUT_KEY_S = 22,
|
||||
INPUT_KEY_T = 23,
|
||||
INPUT_KEY_U = 24,
|
||||
INPUT_KEY_V = 25,
|
||||
INPUT_KEY_W = 26,
|
||||
INPUT_KEY_X = 27,
|
||||
INPUT_KEY_Y = 28,
|
||||
INPUT_KEY_Z = 29,
|
||||
INPUT_KEY_1 = 30,
|
||||
INPUT_KEY_2 = 31,
|
||||
INPUT_KEY_3 = 32,
|
||||
INPUT_KEY_4 = 33,
|
||||
INPUT_KEY_5 = 34,
|
||||
INPUT_KEY_6 = 35,
|
||||
INPUT_KEY_7 = 36,
|
||||
INPUT_KEY_8 = 37,
|
||||
INPUT_KEY_9 = 38,
|
||||
INPUT_KEY_0 = 39,
|
||||
INPUT_KEY_RETURN = 40,
|
||||
INPUT_KEY_ESCAPE = 41,
|
||||
INPUT_KEY_BACKSPACE = 42,
|
||||
INPUT_KEY_TAB = 43,
|
||||
INPUT_KEY_SPACE = 44,
|
||||
INPUT_KEY_MINUS = 45,
|
||||
INPUT_KEY_EQUALS = 46,
|
||||
INPUT_KEY_LEFTBRACKET = 47,
|
||||
INPUT_KEY_RIGHTBRACKET = 48,
|
||||
INPUT_KEY_BACKSLASH = 49,
|
||||
INPUT_KEY_HASH = 50,
|
||||
INPUT_KEY_SEMICOLON = 51,
|
||||
INPUT_KEY_APOSTROPHE = 52,
|
||||
INPUT_KEY_TILDE = 53,
|
||||
INPUT_KEY_COMMA = 54,
|
||||
INPUT_KEY_PERIOD = 55,
|
||||
INPUT_KEY_SLASH = 56,
|
||||
INPUT_KEY_CAPSLOCK = 57,
|
||||
INPUT_KEY_F1 = 58,
|
||||
INPUT_KEY_F2 = 59,
|
||||
INPUT_KEY_F3 = 60,
|
||||
INPUT_KEY_F4 = 61,
|
||||
INPUT_KEY_F5 = 62,
|
||||
INPUT_KEY_F6 = 63,
|
||||
INPUT_KEY_F7 = 64,
|
||||
INPUT_KEY_F8 = 65,
|
||||
INPUT_KEY_F9 = 66,
|
||||
INPUT_KEY_F10 = 67,
|
||||
INPUT_KEY_F11 = 68,
|
||||
INPUT_KEY_F12 = 69,
|
||||
INPUT_KEY_PRINTSCREEN = 70,
|
||||
INPUT_KEY_SCROLLLOCK = 71,
|
||||
INPUT_KEY_PAUSE = 72,
|
||||
INPUT_KEY_INSERT = 73,
|
||||
INPUT_KEY_HOME = 74,
|
||||
INPUT_KEY_PAGEUP = 75,
|
||||
INPUT_KEY_DELETE = 76,
|
||||
INPUT_KEY_END = 77,
|
||||
INPUT_KEY_PAGEDOWN = 78,
|
||||
INPUT_KEY_RIGHT = 79,
|
||||
INPUT_KEY_LEFT = 80,
|
||||
INPUT_KEY_DOWN = 81,
|
||||
INPUT_KEY_UP = 82,
|
||||
INPUT_KEY_NUMLOCK = 83,
|
||||
INPUT_KEY_KP_DIVIDE = 84,
|
||||
INPUT_KEY_KP_MULTIPLY = 85,
|
||||
INPUT_KEY_KP_MINUS = 86,
|
||||
INPUT_KEY_KP_PLUS = 87,
|
||||
INPUT_KEY_KP_ENTER = 88,
|
||||
INPUT_KEY_KP_1 = 89,
|
||||
INPUT_KEY_KP_2 = 90,
|
||||
INPUT_KEY_KP_3 = 91,
|
||||
INPUT_KEY_KP_4 = 92,
|
||||
INPUT_KEY_KP_5 = 93,
|
||||
INPUT_KEY_KP_6 = 94,
|
||||
INPUT_KEY_KP_7 = 95,
|
||||
INPUT_KEY_KP_8 = 96,
|
||||
INPUT_KEY_KP_9 = 97,
|
||||
INPUT_KEY_KP_0 = 98,
|
||||
INPUT_KEY_KP_PERIOD = 99,
|
||||
|
||||
INPUT_KEY_LCTRL = 100,
|
||||
INPUT_KEY_LSHIFT = 101,
|
||||
INPUT_KEY_LALT = 102,
|
||||
INPUT_KEY_LGUI = 103,
|
||||
INPUT_KEY_RCTRL = 104,
|
||||
INPUT_KEY_RSHIFT = 105,
|
||||
INPUT_KEY_RALT = 106,
|
||||
|
||||
INPUT_KEY_MAX = 107,
|
||||
|
||||
INPUT_GAMEPAD_A = 108,
|
||||
INPUT_GAMEPAD_Y = 109,
|
||||
INPUT_GAMEPAD_B = 110,
|
||||
INPUT_GAMEPAD_X = 111,
|
||||
INPUT_GAMEPAD_L_SHOULDER = 112,
|
||||
INPUT_GAMEPAD_R_SHOULDER = 113,
|
||||
INPUT_GAMEPAD_L_TRIGGER = 114,
|
||||
INPUT_GAMEPAD_R_TRIGGER = 115,
|
||||
INPUT_GAMEPAD_SELECT = 116,
|
||||
INPUT_GAMEPAD_START = 117,
|
||||
INPUT_GAMEPAD_L_STICK_PRESS = 118,
|
||||
INPUT_GAMEPAD_R_STICK_PRESS = 119,
|
||||
INPUT_GAMEPAD_DPAD_UP = 120,
|
||||
INPUT_GAMEPAD_DPAD_DOWN = 121,
|
||||
INPUT_GAMEPAD_DPAD_LEFT = 122,
|
||||
INPUT_GAMEPAD_DPAD_RIGHT = 123,
|
||||
INPUT_GAMEPAD_HOME = 124,
|
||||
INPUT_GAMEPAD_L_STICK_UP = 125,
|
||||
INPUT_GAMEPAD_L_STICK_DOWN = 126,
|
||||
INPUT_GAMEPAD_L_STICK_LEFT = 127,
|
||||
INPUT_GAMEPAD_L_STICK_RIGHT = 128,
|
||||
INPUT_GAMEPAD_R_STICK_UP = 129,
|
||||
INPUT_GAMEPAD_R_STICK_DOWN = 130,
|
||||
INPUT_GAMEPAD_R_STICK_LEFT = 131,
|
||||
INPUT_GAMEPAD_R_STICK_RIGHT = 132,
|
||||
|
||||
INPUT_MOUSE_LEFT = 134,
|
||||
INPUT_MOUSE_MIDDLE = 135,
|
||||
INPUT_MOUSE_RIGHT = 136,
|
||||
INPUT_MOUSE_WHEEL_UP = 137,
|
||||
INPUT_MOUSE_WHEEL_DOWN = 138,
|
||||
|
||||
INPUT_BUTTON_MAX = 139
|
||||
} button_t;
|
||||
|
||||
typedef enum {
|
||||
INPUT_LAYER_SYSTEM = 0,
|
||||
INPUT_LAYER_USER = 1,
|
||||
INPUT_LAYER_MAX = 2
|
||||
} input_layer_t;
|
||||
|
||||
typedef void(*input_capture_callback_t)
|
||||
(void *user, button_t button, int32_t ascii_char);
|
||||
|
||||
void input_init();
|
||||
void input_cleanup();
|
||||
void input_clear();
|
||||
|
||||
void input_bind(input_layer_t layer, button_t button, uint8_t action);
|
||||
void input_unbind(input_layer_t layer,button_t button);
|
||||
void input_unbind_all(input_layer_t layer);
|
||||
|
||||
uint8_t input_bound_to_action(button_t button);
|
||||
|
||||
void input_set_button_state(button_t button, float state);
|
||||
void input_set_mouse_pos(int32_t x, int32_t y);
|
||||
void input_textinput(int32_t ascii_char);
|
||||
|
||||
void input_capture(input_capture_callback_t cb, void *user);
|
||||
|
||||
float input_state(uint8_t action);
|
||||
bool input_pressed(uint8_t action);
|
||||
bool input_released(uint8_t action);
|
||||
vec2_t input_mouse_pos();
|
||||
|
||||
button_t input_name_to_button(const char *name);
|
||||
const char *input_button_to_name(button_t button);
|
||||
|
||||
|
||||
#define INPUT_ACTION_COMMAND 31
|
||||
#define INPUT_ACTION_MAX 32
|
||||
#define INPUT_DEADZONE 0.1
|
||||
#define INPUT_ACTION_NONE 255
|
||||
#define INPUT_BUTTON_NONE 0
|
||||
|
||||
#endif
|
||||
+4274
File diff suppressed because it is too large
Load Diff
+728
File diff suppressed because it is too large
Load Diff
+11057
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,324 @@
|
||||
#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL)
|
||||
#define SOKOL_TIME_IMPL
|
||||
#endif
|
||||
#ifndef SOKOL_TIME_INCLUDED
|
||||
/*
|
||||
sokol_time.h -- simple cross-platform time measurement
|
||||
|
||||
Project URL: https://github.com/floooh/sokol
|
||||
|
||||
Do this:
|
||||
#define SOKOL_IMPL or
|
||||
#define SOKOL_TIME_IMPL
|
||||
before you include this file in *one* C or C++ file to create the
|
||||
implementation.
|
||||
|
||||
Optionally provide the following defines with your own implementations:
|
||||
SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
|
||||
SOKOL_TIME_API_DECL - public function declaration prefix (default: extern)
|
||||
SOKOL_API_DECL - same as SOKOL_TIME_API_DECL
|
||||
SOKOL_API_IMPL - public function implementation prefix (default: -)
|
||||
|
||||
If sokol_time.h is compiled as a DLL, define the following before
|
||||
including the declaration or implementation:
|
||||
|
||||
SOKOL_DLL
|
||||
|
||||
On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport)
|
||||
or __declspec(dllimport) as needed.
|
||||
|
||||
void stm_setup();
|
||||
Call once before any other functions to initialize sokol_time
|
||||
(this calls for instance QueryPerformanceFrequency on Windows)
|
||||
|
||||
uint64_t stm_now();
|
||||
Get current point in time in unspecified 'ticks'. The value that
|
||||
is returned has no relation to the 'wall-clock' time and is
|
||||
not in a specific time unit, it is only useful to compute
|
||||
time differences.
|
||||
|
||||
uint64_t stm_diff(uint64_t new, uint64_t old);
|
||||
Computes the time difference between new and old. This will always
|
||||
return a positive, non-zero value.
|
||||
|
||||
uint64_t stm_since(uint64_t start);
|
||||
Takes the current time, and returns the elapsed time since start
|
||||
(this is a shortcut for "stm_diff(stm_now(), start)")
|
||||
|
||||
uint64_t stm_laptime(uint64_t* last_time);
|
||||
This is useful for measuring frame time and other recurring
|
||||
events. It takes the current time, returns the time difference
|
||||
to the value in last_time, and stores the current time in
|
||||
last_time for the next call. If the value in last_time is 0,
|
||||
the return value will be zero (this usually happens on the
|
||||
very first call).
|
||||
|
||||
uint64_t stm_round_to_common_refresh_rate(uint64_t duration)
|
||||
This oddly named function takes a measured frame time and
|
||||
returns the closest "nearby" common display refresh rate frame duration
|
||||
in ticks. If the input duration isn't close to any common display
|
||||
refresh rate, the input duration will be returned unchanged as a fallback.
|
||||
The main purpose of this function is to remove jitter/inaccuracies from
|
||||
measured frame times, and instead use the display refresh rate as
|
||||
frame duration.
|
||||
|
||||
Use the following functions to convert a duration in ticks into
|
||||
useful time units:
|
||||
|
||||
double stm_sec(uint64_t ticks);
|
||||
double stm_ms(uint64_t ticks);
|
||||
double stm_us(uint64_t ticks);
|
||||
double stm_ns(uint64_t ticks);
|
||||
Converts a tick value into seconds, milliseconds, microseconds
|
||||
or nanoseconds. Note that not all platforms will have nanosecond
|
||||
or even microsecond precision.
|
||||
|
||||
Uses the following time measurement functions under the hood:
|
||||
|
||||
Windows: QueryPerformanceFrequency() / QueryPerformanceCounter()
|
||||
MacOS/iOS: mach_absolute_time()
|
||||
emscripten: performance.now()
|
||||
Linux+others: clock_gettime(CLOCK_MONOTONIC)
|
||||
|
||||
zlib/libpng license
|
||||
|
||||
Copyright (c) 2018 Andre Weissflog
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the
|
||||
use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software in a
|
||||
product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not
|
||||
be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
#define SOKOL_TIME_INCLUDED (1)
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL)
|
||||
#define SOKOL_TIME_API_DECL SOKOL_API_DECL
|
||||
#endif
|
||||
#ifndef SOKOL_TIME_API_DECL
|
||||
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL)
|
||||
#define SOKOL_TIME_API_DECL __declspec(dllexport)
|
||||
#elif defined(_WIN32) && defined(SOKOL_DLL)
|
||||
#define SOKOL_TIME_API_DECL __declspec(dllimport)
|
||||
#else
|
||||
#define SOKOL_TIME_API_DECL extern
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
SOKOL_TIME_API_DECL void stm_setup(void);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_now(void);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks);
|
||||
SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_us(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
#endif // SOKOL_TIME_INCLUDED
|
||||
|
||||
/*-- IMPLEMENTATION ----------------------------------------------------------*/
|
||||
#ifdef SOKOL_TIME_IMPL
|
||||
#define SOKOL_TIME_IMPL_INCLUDED (1)
|
||||
#include <string.h> /* memset */
|
||||
|
||||
#ifndef SOKOL_API_IMPL
|
||||
#define SOKOL_API_IMPL
|
||||
#endif
|
||||
#ifndef SOKOL_ASSERT
|
||||
#include <assert.h>
|
||||
#define SOKOL_ASSERT(c) assert(c)
|
||||
#endif
|
||||
#ifndef _SOKOL_PRIVATE
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define _SOKOL_PRIVATE __attribute__((unused)) static
|
||||
#else
|
||||
#define _SOKOL_PRIVATE static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
LARGE_INTEGER freq;
|
||||
LARGE_INTEGER start;
|
||||
} _stm_state_t;
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
#include <mach/mach_time.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
mach_timebase_info_data_t timebase;
|
||||
uint64_t start;
|
||||
} _stm_state_t;
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
#include <emscripten/emscripten.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
double start;
|
||||
} _stm_state_t;
|
||||
#else /* anything else, this will need more care for non-Linux platforms */
|
||||
#ifdef ESP8266
|
||||
// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined
|
||||
#define CLOCK_MONOTONIC 0
|
||||
#endif
|
||||
#include <time.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
uint64_t start;
|
||||
} _stm_state_t;
|
||||
#endif
|
||||
static _stm_state_t _stm;
|
||||
|
||||
/* prevent 64-bit overflow when computing relative timestamp
|
||||
see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3
|
||||
*/
|
||||
#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__))
|
||||
_SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) {
|
||||
int64_t q = value / denom;
|
||||
int64_t r = value % denom;
|
||||
return q * numer + r * numer / denom;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
EM_JS(double, stm_js_perfnow, (void), {
|
||||
return performance.now();
|
||||
});
|
||||
#endif
|
||||
|
||||
SOKOL_API_IMPL void stm_setup(void) {
|
||||
memset(&_stm, 0, sizeof(_stm));
|
||||
_stm.initialized = 0xABCDABCD;
|
||||
#if defined(_WIN32)
|
||||
QueryPerformanceFrequency(&_stm.freq);
|
||||
QueryPerformanceCounter(&_stm.start);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
mach_timebase_info(&_stm.timebase);
|
||||
_stm.start = mach_absolute_time();
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
_stm.start = stm_js_perfnow();
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
_stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_now(void) {
|
||||
SOKOL_ASSERT(_stm.initialized == 0xABCDABCD);
|
||||
uint64_t now;
|
||||
#if defined(_WIN32)
|
||||
LARGE_INTEGER qpc_t;
|
||||
QueryPerformanceCounter(&qpc_t);
|
||||
now = (uint64_t) int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
const uint64_t mach_now = mach_absolute_time() - _stm.start;
|
||||
now = (uint64_t) int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom);
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
double js_now = stm_js_perfnow() - _stm.start;
|
||||
SOKOL_ASSERT(js_now >= 0.0);
|
||||
now = (uint64_t) (js_now * 1000000.0);
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start;
|
||||
#endif
|
||||
return now;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) {
|
||||
if (new_ticks > old_ticks) {
|
||||
return new_ticks - old_ticks;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) {
|
||||
return stm_diff(stm_now(), start_ticks);
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) {
|
||||
SOKOL_ASSERT(last_time);
|
||||
uint64_t dt = 0;
|
||||
uint64_t now = stm_now();
|
||||
if (0 != *last_time) {
|
||||
dt = stm_diff(now, *last_time);
|
||||
}
|
||||
*last_time = now;
|
||||
return dt;
|
||||
}
|
||||
|
||||
// first number is frame duration in ns, second number is tolerance in ns,
|
||||
// the resulting min/max values must not overlap!
|
||||
static const uint64_t _stm_refresh_rates[][2] = {
|
||||
{ 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms
|
||||
{ 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms
|
||||
{ 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms
|
||||
{ 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25
|
||||
{ 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms
|
||||
{ 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms
|
||||
{ 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms
|
||||
{ 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms
|
||||
{ 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms
|
||||
{ 0, 0 }, // keep the last element always at zero
|
||||
};
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) {
|
||||
uint64_t ns;
|
||||
int i = 0;
|
||||
while (0 != (ns = _stm_refresh_rates[i][0])) {
|
||||
uint64_t tol = _stm_refresh_rates[i][1];
|
||||
if ((ticks > (ns - tol)) && (ticks < (ns + tol))) {
|
||||
return ns;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// fallthough: didn't fit into any buckets
|
||||
return ticks;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_sec(uint64_t ticks) {
|
||||
return (double)ticks / 1000000000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_ms(uint64_t ticks) {
|
||||
return (double)ticks / 1000000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_us(uint64_t ticks) {
|
||||
return (double)ticks / 1000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_ns(uint64_t ticks) {
|
||||
return (double)ticks;
|
||||
}
|
||||
#endif /* SOKOL_TIME_IMPL */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mem.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
static uint8_t hunk[MEM_HUNK_BYTES];
|
||||
static uint32_t bump_len = 0;
|
||||
static uint32_t temp_len = 0;
|
||||
|
||||
static uint32_t temp_objects[MEM_TEMP_OBJECTS_MAX] = {};
|
||||
static uint32_t temp_objects_len;
|
||||
|
||||
|
||||
// Bump allocator - returns bytes from the front of the hunk
|
||||
|
||||
// These allocations persist for many frames. The allocator level is reset
|
||||
// whenever we load a new race track or menu in game_set_scene()
|
||||
|
||||
void *mem_mark() {
|
||||
return &hunk[bump_len];
|
||||
}
|
||||
|
||||
void *mem_bump(uint32_t size) {
|
||||
error_if(bump_len + temp_len + size >= MEM_HUNK_BYTES, "Failed to allocate %d bytes in hunk mem", size);
|
||||
uint8_t *p = &hunk[bump_len];
|
||||
bump_len += size;
|
||||
memset(p, 0, size);
|
||||
return p;
|
||||
}
|
||||
|
||||
void mem_reset(void *p) {
|
||||
uint32_t offset = (uint8_t *)p - (uint8_t *)hunk;
|
||||
error_if(offset > bump_len || offset > MEM_HUNK_BYTES, "Invalid mem reset");
|
||||
bump_len = offset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Temp allocator - returns bytes from the back of the hunk
|
||||
|
||||
// Temporary allocated bytes are not allowed to persist for multiple frames. You
|
||||
// need to explicitly free them when you are done. Temp allocated bytes don't
|
||||
// have be freed in reverse allocation order. I.e. you can allocate A then B,
|
||||
// and aftewards free A then B.
|
||||
|
||||
void *mem_temp_alloc(uint32_t size) {
|
||||
size = ((size >> 3) + 7) << 3; // allign to 8 bytes
|
||||
|
||||
error_if(bump_len + temp_len + size >= MEM_HUNK_BYTES, "Failed to allocate %d bytes in temp mem", size);
|
||||
error_if(temp_objects_len >= MEM_TEMP_OBJECTS_MAX, "MEM_TEMP_OBJECTS_MAX reached");
|
||||
|
||||
temp_len += size;
|
||||
void *p = &hunk[MEM_HUNK_BYTES - temp_len];
|
||||
temp_objects[temp_objects_len++] = temp_len;
|
||||
return p;
|
||||
}
|
||||
|
||||
void mem_temp_free(void *p) {
|
||||
uint32_t offset = (uint8_t *)&hunk[MEM_HUNK_BYTES] - (uint8_t *)p;
|
||||
error_if(offset > MEM_HUNK_BYTES, "Object 0x%p not in temp hunk", p);
|
||||
|
||||
bool found = false;
|
||||
uint32_t remaining_max = 0;
|
||||
for (int i = 0; i < temp_objects_len; i++) {
|
||||
if (temp_objects[i] == offset) {
|
||||
temp_objects[i--] = temp_objects[--temp_objects_len];
|
||||
found = true;
|
||||
}
|
||||
else if (temp_objects[i] > remaining_max) {
|
||||
remaining_max = temp_objects[i];
|
||||
}
|
||||
}
|
||||
error_if(!found, "Object 0x%p not in temp hunk", p);
|
||||
temp_len = remaining_max;
|
||||
}
|
||||
|
||||
void mem_temp_check() {
|
||||
error_if(temp_len != 0, "Temp memory not free: %d object(s)", temp_objects_len);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef MEM_H
|
||||
#define MEM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define MEM_TEMP_OBJECTS_MAX 8
|
||||
#define MEM_HUNK_BYTES (4 * 1024 * 1024)
|
||||
|
||||
void *mem_bump(uint32_t size);
|
||||
void *mem_mark();
|
||||
void mem_reset(void *p);
|
||||
|
||||
void *mem_temp_alloc(uint32_t size);
|
||||
void mem_temp_free(void *p);
|
||||
void mem_temp_check();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef PLATFORM_H
|
||||
#define PLATFORM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
void platform_exit();
|
||||
vec2i_t platform_screen_size();
|
||||
double platform_now();
|
||||
void platform_set_fullscreen(bool fullscreen);
|
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len));
|
||||
|
||||
#endif
|
||||
Executable
+281
@@ -0,0 +1,281 @@
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "input.h"
|
||||
#include "system.h"
|
||||
|
||||
static uint64_t perf_freq = 0;
|
||||
static bool wants_to_exit = false;
|
||||
static SDL_Window *window;
|
||||
static SDL_AudioDeviceID audio_device;
|
||||
static SDL_GameController *gamepad;
|
||||
static void (*audio_callback)(float *buffer, uint32_t len) = NULL;
|
||||
|
||||
|
||||
uint8_t platform_sdl_gamepad_map[] = {
|
||||
[SDL_CONTROLLER_BUTTON_A] = INPUT_GAMEPAD_A,
|
||||
[SDL_CONTROLLER_BUTTON_B] = INPUT_GAMEPAD_B,
|
||||
[SDL_CONTROLLER_BUTTON_X] = INPUT_GAMEPAD_X,
|
||||
[SDL_CONTROLLER_BUTTON_Y] = INPUT_GAMEPAD_Y,
|
||||
[SDL_CONTROLLER_BUTTON_BACK] = INPUT_GAMEPAD_SELECT,
|
||||
[SDL_CONTROLLER_BUTTON_GUIDE] = INPUT_INVALID,
|
||||
[SDL_CONTROLLER_BUTTON_START] = INPUT_GAMEPAD_START,
|
||||
[SDL_CONTROLLER_BUTTON_LEFTSTICK] = INPUT_GAMEPAD_L_STICK_PRESS,
|
||||
[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = INPUT_GAMEPAD_R_STICK_PRESS,
|
||||
[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = INPUT_GAMEPAD_L_SHOULDER,
|
||||
[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = INPUT_GAMEPAD_R_SHOULDER,
|
||||
[SDL_CONTROLLER_BUTTON_DPAD_UP] = INPUT_GAMEPAD_DPAD_UP,
|
||||
[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = INPUT_GAMEPAD_DPAD_DOWN,
|
||||
[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = INPUT_GAMEPAD_DPAD_LEFT,
|
||||
[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = INPUT_GAMEPAD_DPAD_RIGHT,
|
||||
[SDL_CONTROLLER_BUTTON_MAX] = INPUT_INVALID
|
||||
};
|
||||
|
||||
|
||||
uint8_t platform_sdl_axis_map[] = {
|
||||
[SDL_CONTROLLER_AXIS_LEFTX] = INPUT_GAMEPAD_L_STICK_LEFT,
|
||||
[SDL_CONTROLLER_AXIS_LEFTY] = INPUT_GAMEPAD_L_STICK_UP,
|
||||
[SDL_CONTROLLER_AXIS_RIGHTX] = INPUT_GAMEPAD_R_STICK_LEFT,
|
||||
[SDL_CONTROLLER_AXIS_RIGHTY] = INPUT_GAMEPAD_R_STICK_UP,
|
||||
[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = INPUT_GAMEPAD_L_TRIGGER,
|
||||
[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = INPUT_GAMEPAD_R_TRIGGER,
|
||||
[SDL_CONTROLLER_AXIS_MAX] = INPUT_INVALID
|
||||
};
|
||||
|
||||
|
||||
void platform_exit() {
|
||||
wants_to_exit = true;
|
||||
}
|
||||
|
||||
SDL_GameController *platform_find_gamepad() {
|
||||
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
||||
if (SDL_IsGameController(i)) {
|
||||
return SDL_GameControllerOpen(i);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void platform_pump_events() {
|
||||
SDL_Event ev;
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
// Input Keyboard
|
||||
if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) {
|
||||
int code = ev.key.keysym.scancode;
|
||||
float state = ev.type == SDL_KEYDOWN ? 1.0 : 0.0;
|
||||
if (code >= SDL_SCANCODE_LCTRL && code <= SDL_SCANCODE_RALT) {
|
||||
int code_internal = code - SDL_SCANCODE_LCTRL + INPUT_KEY_LCTRL;
|
||||
input_set_button_state(code_internal, state);
|
||||
}
|
||||
else if (code > 0 && code < INPUT_KEY_MAX) {
|
||||
input_set_button_state(code, state);
|
||||
}
|
||||
}
|
||||
|
||||
else if (ev.type == SDL_TEXTINPUT) {
|
||||
input_textinput(ev.text.text[0]);
|
||||
}
|
||||
|
||||
// Gamepads connect/disconnect
|
||||
else if (ev.type == SDL_CONTROLLERDEVICEADDED) {
|
||||
gamepad = SDL_GameControllerOpen(ev.cdevice.which);
|
||||
}
|
||||
else if (ev.type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
if (gamepad && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepad))) {
|
||||
SDL_GameControllerClose(gamepad);
|
||||
gamepad = platform_find_gamepad();
|
||||
}
|
||||
}
|
||||
|
||||
// Input Gamepad Buttons
|
||||
else if (
|
||||
ev.type == SDL_CONTROLLERBUTTONDOWN ||
|
||||
ev.type == SDL_CONTROLLERBUTTONUP
|
||||
) {
|
||||
if (ev.cbutton.button < SDL_CONTROLLER_BUTTON_MAX) {
|
||||
button_t button = platform_sdl_gamepad_map[ev.cbutton.button];
|
||||
if (button != INPUT_INVALID) {
|
||||
float state = ev.type == SDL_CONTROLLERBUTTONDOWN ? 1.0 : 0.0;
|
||||
input_set_button_state(button, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Gamepad Axis
|
||||
else if (ev.type == SDL_CONTROLLERAXISMOTION) {
|
||||
float state = (float)ev.caxis.value / 32767.0;
|
||||
|
||||
if (ev.caxis.axis < SDL_CONTROLLER_AXIS_MAX) {
|
||||
int code = platform_sdl_axis_map[ev.caxis.axis];
|
||||
if (
|
||||
code == INPUT_GAMEPAD_L_TRIGGER ||
|
||||
code == INPUT_GAMEPAD_R_TRIGGER
|
||||
) {
|
||||
input_set_button_state(code, state);
|
||||
}
|
||||
else if (state > 0) {
|
||||
input_set_button_state(code, 0.0);
|
||||
input_set_button_state(code+1, state);
|
||||
}
|
||||
else {
|
||||
input_set_button_state(code, -state);
|
||||
input_set_button_state(code+1, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse buttons
|
||||
else if (
|
||||
ev.type == SDL_MOUSEBUTTONDOWN ||
|
||||
ev.type == SDL_MOUSEBUTTONUP
|
||||
) {
|
||||
button_t button = INPUT_BUTTON_NONE;
|
||||
switch (ev.button.button) {
|
||||
case SDL_BUTTON_LEFT: button = INPUT_MOUSE_LEFT; break;
|
||||
case SDL_BUTTON_MIDDLE: button = INPUT_MOUSE_MIDDLE; break;
|
||||
case SDL_BUTTON_RIGHT: button = INPUT_MOUSE_RIGHT; break;
|
||||
default: break;
|
||||
}
|
||||
if (button != INPUT_BUTTON_NONE) {
|
||||
float state = ev.type == SDL_MOUSEBUTTONDOWN ? 1.0 : 0.0;
|
||||
input_set_button_state(button, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse wheel
|
||||
else if (ev.type == SDL_MOUSEWHEEL) {
|
||||
button_t button = ev.wheel.y > 0
|
||||
? INPUT_MOUSE_WHEEL_UP
|
||||
: INPUT_MOUSE_WHEEL_DOWN;
|
||||
input_set_button_state(button, 1.0);
|
||||
input_set_button_state(button, 0.0);
|
||||
}
|
||||
|
||||
// Mouse move
|
||||
else if (ev.type == SDL_MOUSEMOTION) {
|
||||
input_set_mouse_pos(ev.motion.x, ev.motion.y);
|
||||
}
|
||||
|
||||
// Window Events
|
||||
if (ev.type == SDL_QUIT) {
|
||||
wants_to_exit = true;
|
||||
}
|
||||
else if (
|
||||
ev.type == SDL_WINDOWEVENT &&
|
||||
(
|
||||
ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ||
|
||||
ev.window.event == SDL_WINDOWEVENT_RESIZED
|
||||
)
|
||||
) {
|
||||
system_resize(platform_screen_size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec2i_t platform_screen_size() {
|
||||
int width, height;
|
||||
SDL_GL_GetDrawableSize(window, &width, &height);
|
||||
return vec2i(width, height);
|
||||
}
|
||||
|
||||
double platform_now() {
|
||||
uint64_t perf_counter = SDL_GetPerformanceCounter();
|
||||
return (double)perf_counter / (double)perf_freq;
|
||||
}
|
||||
|
||||
void platform_set_fullscreen(bool fullscreen) {
|
||||
if (fullscreen) {
|
||||
int32_t display = SDL_GetWindowDisplayIndex(window);
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
SDL_GetDesktopDisplayMode(display, &mode);
|
||||
SDL_SetWindowDisplayMode(window, &mode);
|
||||
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
|
||||
}
|
||||
else {
|
||||
SDL_SetWindowFullscreen(window, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void platform_audio_callback(void* userdata, uint8_t* stream, int len) {
|
||||
if (audio_callback) {
|
||||
audio_callback((float *)stream, len/sizeof(float));
|
||||
}
|
||||
else {
|
||||
memset(stream, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)) {
|
||||
audio_callback = cb;
|
||||
SDL_PauseAudioDevice(audio_device, 0);
|
||||
}
|
||||
|
||||
|
||||
#if defined(RENDERER_GL)
|
||||
#define PLATFORM_WINDOW_FLAGS SDL_WINDOW_OPENGL
|
||||
SDL_GLContext platform_gl;
|
||||
|
||||
void platform_video_init() {
|
||||
platform_gl = SDL_GL_CreateContext(window);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
}
|
||||
|
||||
void platform_prepare_frame() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
void platform_video_cleanup() {
|
||||
SDL_GL_DeleteContext(platform_gl);
|
||||
}
|
||||
|
||||
void platform_end_frame() {
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
#else
|
||||
#error "Unsupported renderer for platform SDL"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER);
|
||||
|
||||
audio_device = SDL_OpenAudioDevice(NULL, 0, &(SDL_AudioSpec){
|
||||
.freq = 44100,
|
||||
.format = AUDIO_F32,
|
||||
.channels = 2,
|
||||
.samples = 4096,
|
||||
.callback = platform_audio_callback
|
||||
}, NULL, 0);
|
||||
|
||||
gamepad = platform_find_gamepad();
|
||||
|
||||
perf_freq = SDL_GetPerformanceFrequency();
|
||||
|
||||
window = SDL_CreateWindow(
|
||||
SYSTEM_WINDOW_NAME,
|
||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
SYSTEM_WINDOW_WIDTH, SYSTEM_WINDOW_HEIGHT,
|
||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | PLATFORM_WINDOW_FLAGS | SDL_WINDOW_ALLOW_HIGHDPI
|
||||
);
|
||||
|
||||
platform_video_init();
|
||||
system_init();
|
||||
|
||||
while (!wants_to_exit) {
|
||||
platform_pump_events();
|
||||
platform_prepare_frame();
|
||||
system_update();
|
||||
platform_end_frame();
|
||||
}
|
||||
|
||||
system_cleanup();
|
||||
platform_video_cleanup();
|
||||
|
||||
SDL_CloseAudioDevice(audio_device);
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
|
||||
#if defined(RENDERER_GL)
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#define SOKOL_GLES2
|
||||
#else
|
||||
#define SOKOL_GLCORE33
|
||||
#endif
|
||||
#else
|
||||
#error "Unsupported renderer for platform SOKOL"
|
||||
#endif
|
||||
|
||||
#define SOKOL_IMPL
|
||||
#include "libs/sokol_audio.h"
|
||||
#include "libs/sokol_time.h"
|
||||
#include "libs/sokol_app.h"
|
||||
#include "input.h"
|
||||
|
||||
static const uint8_t keyboard_map[] = {
|
||||
[SAPP_KEYCODE_SPACE] = INPUT_KEY_SPACE,
|
||||
[SAPP_KEYCODE_APOSTROPHE] = INPUT_KEY_APOSTROPHE,
|
||||
[SAPP_KEYCODE_COMMA] = INPUT_KEY_COMMA,
|
||||
[SAPP_KEYCODE_MINUS] = INPUT_KEY_MINUS,
|
||||
[SAPP_KEYCODE_PERIOD] = INPUT_KEY_PERIOD,
|
||||
[SAPP_KEYCODE_SLASH] = INPUT_KEY_SLASH,
|
||||
[SAPP_KEYCODE_0] = INPUT_KEY_0,
|
||||
[SAPP_KEYCODE_1] = INPUT_KEY_1,
|
||||
[SAPP_KEYCODE_2] = INPUT_KEY_2,
|
||||
[SAPP_KEYCODE_3] = INPUT_KEY_3,
|
||||
[SAPP_KEYCODE_4] = INPUT_KEY_4,
|
||||
[SAPP_KEYCODE_5] = INPUT_KEY_5,
|
||||
[SAPP_KEYCODE_6] = INPUT_KEY_6,
|
||||
[SAPP_KEYCODE_7] = INPUT_KEY_7,
|
||||
[SAPP_KEYCODE_8] = INPUT_KEY_8,
|
||||
[SAPP_KEYCODE_9] = INPUT_KEY_9,
|
||||
[SAPP_KEYCODE_SEMICOLON] = INPUT_KEY_SEMICOLON,
|
||||
[SAPP_KEYCODE_EQUAL] = INPUT_KEY_EQUALS,
|
||||
[SAPP_KEYCODE_A] = INPUT_KEY_A,
|
||||
[SAPP_KEYCODE_B] = INPUT_KEY_B,
|
||||
[SAPP_KEYCODE_C] = INPUT_KEY_C,
|
||||
[SAPP_KEYCODE_D] = INPUT_KEY_D,
|
||||
[SAPP_KEYCODE_E] = INPUT_KEY_E,
|
||||
[SAPP_KEYCODE_F] = INPUT_KEY_F,
|
||||
[SAPP_KEYCODE_G] = INPUT_KEY_G,
|
||||
[SAPP_KEYCODE_H] = INPUT_KEY_H,
|
||||
[SAPP_KEYCODE_I] = INPUT_KEY_I,
|
||||
[SAPP_KEYCODE_J] = INPUT_KEY_J,
|
||||
[SAPP_KEYCODE_K] = INPUT_KEY_K,
|
||||
[SAPP_KEYCODE_L] = INPUT_KEY_L,
|
||||
[SAPP_KEYCODE_M] = INPUT_KEY_M,
|
||||
[SAPP_KEYCODE_N] = INPUT_KEY_N,
|
||||
[SAPP_KEYCODE_O] = INPUT_KEY_O,
|
||||
[SAPP_KEYCODE_P] = INPUT_KEY_P,
|
||||
[SAPP_KEYCODE_Q] = INPUT_KEY_Q,
|
||||
[SAPP_KEYCODE_R] = INPUT_KEY_R,
|
||||
[SAPP_KEYCODE_S] = INPUT_KEY_S,
|
||||
[SAPP_KEYCODE_T] = INPUT_KEY_T,
|
||||
[SAPP_KEYCODE_U] = INPUT_KEY_U,
|
||||
[SAPP_KEYCODE_V] = INPUT_KEY_V,
|
||||
[SAPP_KEYCODE_W] = INPUT_KEY_W,
|
||||
[SAPP_KEYCODE_X] = INPUT_KEY_X,
|
||||
[SAPP_KEYCODE_Y] = INPUT_KEY_Y,
|
||||
[SAPP_KEYCODE_Z] = INPUT_KEY_Z,
|
||||
[SAPP_KEYCODE_LEFT_BRACKET] = INPUT_KEY_LEFTBRACKET,
|
||||
[SAPP_KEYCODE_BACKSLASH] = INPUT_KEY_BACKSLASH,
|
||||
[SAPP_KEYCODE_RIGHT_BRACKET] = INPUT_KEY_RIGHTBRACKET,
|
||||
[SAPP_KEYCODE_GRAVE_ACCENT] = INPUT_KEY_TILDE,
|
||||
[SAPP_KEYCODE_WORLD_1] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_WORLD_2] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_ESCAPE] = INPUT_KEY_ESCAPE,
|
||||
[SAPP_KEYCODE_ENTER] = INPUT_KEY_RETURN,
|
||||
[SAPP_KEYCODE_TAB] = INPUT_KEY_TAB,
|
||||
[SAPP_KEYCODE_BACKSPACE] = INPUT_KEY_BACKSPACE,
|
||||
[SAPP_KEYCODE_INSERT] = INPUT_KEY_INSERT,
|
||||
[SAPP_KEYCODE_DELETE] = INPUT_KEY_DELETE,
|
||||
[SAPP_KEYCODE_RIGHT] = INPUT_KEY_RIGHT,
|
||||
[SAPP_KEYCODE_LEFT] = INPUT_KEY_LEFT,
|
||||
[SAPP_KEYCODE_DOWN] = INPUT_KEY_DOWN,
|
||||
[SAPP_KEYCODE_UP] = INPUT_KEY_UP,
|
||||
[SAPP_KEYCODE_PAGE_UP] = INPUT_KEY_PAGEUP,
|
||||
[SAPP_KEYCODE_PAGE_DOWN] = INPUT_KEY_PAGEDOWN,
|
||||
[SAPP_KEYCODE_HOME] = INPUT_KEY_HOME,
|
||||
[SAPP_KEYCODE_END] = INPUT_KEY_END,
|
||||
[SAPP_KEYCODE_CAPS_LOCK] = INPUT_KEY_CAPSLOCK,
|
||||
[SAPP_KEYCODE_SCROLL_LOCK] = INPUT_KEY_SCROLLLOCK,
|
||||
[SAPP_KEYCODE_NUM_LOCK] = INPUT_KEY_NUMLOCK,
|
||||
[SAPP_KEYCODE_PRINT_SCREEN] = INPUT_KEY_PRINTSCREEN,
|
||||
[SAPP_KEYCODE_PAUSE] = INPUT_KEY_PAUSE,
|
||||
[SAPP_KEYCODE_F1] = INPUT_KEY_F1,
|
||||
[SAPP_KEYCODE_F2] = INPUT_KEY_F2,
|
||||
[SAPP_KEYCODE_F3] = INPUT_KEY_F3,
|
||||
[SAPP_KEYCODE_F4] = INPUT_KEY_F4,
|
||||
[SAPP_KEYCODE_F5] = INPUT_KEY_F5,
|
||||
[SAPP_KEYCODE_F6] = INPUT_KEY_F6,
|
||||
[SAPP_KEYCODE_F7] = INPUT_KEY_F7,
|
||||
[SAPP_KEYCODE_F8] = INPUT_KEY_F8,
|
||||
[SAPP_KEYCODE_F9] = INPUT_KEY_F9,
|
||||
[SAPP_KEYCODE_F10] = INPUT_KEY_F10,
|
||||
[SAPP_KEYCODE_F11] = INPUT_KEY_F11,
|
||||
[SAPP_KEYCODE_F12] = INPUT_KEY_F12,
|
||||
[SAPP_KEYCODE_F13] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F14] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F15] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F16] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F17] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F18] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F19] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F20] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F21] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F22] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F23] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F24] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F25] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_KP_0] = INPUT_KEY_KP_0,
|
||||
[SAPP_KEYCODE_KP_1] = INPUT_KEY_KP_1,
|
||||
[SAPP_KEYCODE_KP_2] = INPUT_KEY_KP_2,
|
||||
[SAPP_KEYCODE_KP_3] = INPUT_KEY_KP_3,
|
||||
[SAPP_KEYCODE_KP_4] = INPUT_KEY_KP_4,
|
||||
[SAPP_KEYCODE_KP_5] = INPUT_KEY_KP_5,
|
||||
[SAPP_KEYCODE_KP_6] = INPUT_KEY_KP_6,
|
||||
[SAPP_KEYCODE_KP_7] = INPUT_KEY_KP_7,
|
||||
[SAPP_KEYCODE_KP_8] = INPUT_KEY_KP_8,
|
||||
[SAPP_KEYCODE_KP_9] = INPUT_KEY_KP_9,
|
||||
[SAPP_KEYCODE_KP_DECIMAL] = INPUT_KEY_KP_PERIOD,
|
||||
[SAPP_KEYCODE_KP_DIVIDE] = INPUT_KEY_KP_DIVIDE,
|
||||
[SAPP_KEYCODE_KP_MULTIPLY] = INPUT_KEY_KP_MULTIPLY,
|
||||
[SAPP_KEYCODE_KP_SUBTRACT] = INPUT_KEY_KP_MINUS,
|
||||
[SAPP_KEYCODE_KP_ADD] = INPUT_KEY_KP_PLUS,
|
||||
[SAPP_KEYCODE_KP_ENTER] = INPUT_KEY_KP_ENTER,
|
||||
[SAPP_KEYCODE_KP_EQUAL] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_LEFT_SHIFT] = INPUT_KEY_LSHIFT,
|
||||
[SAPP_KEYCODE_LEFT_CONTROL] = INPUT_KEY_LCTRL,
|
||||
[SAPP_KEYCODE_LEFT_ALT] = INPUT_KEY_LALT,
|
||||
[SAPP_KEYCODE_LEFT_SUPER] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_RIGHT_SHIFT] = INPUT_KEY_RSHIFT,
|
||||
[SAPP_KEYCODE_RIGHT_CONTROL] = INPUT_KEY_RCTRL,
|
||||
[SAPP_KEYCODE_RIGHT_ALT] = INPUT_KEY_RALT,
|
||||
[SAPP_KEYCODE_RIGHT_SUPER] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_MENU] = INPUT_INVALID, // not implemented
|
||||
};
|
||||
|
||||
|
||||
static void (*audio_callback)(float *buffer, uint32_t len) = NULL;
|
||||
|
||||
void platform_exit() {
|
||||
sapp_quit();
|
||||
}
|
||||
|
||||
vec2i_t platform_screen_size() {
|
||||
return vec2i(sapp_width(), sapp_height());
|
||||
}
|
||||
|
||||
double platform_now() {
|
||||
return stm_sec(stm_now());
|
||||
}
|
||||
|
||||
void platform_set_fullscreen(bool fullscreen) {
|
||||
if (fullscreen == sapp_is_fullscreen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sapp_toggle_fullscreen();
|
||||
}
|
||||
|
||||
void platform_handle_event(const sapp_event* ev) {
|
||||
// Input Keyboard
|
||||
if (ev->type == SAPP_EVENTTYPE_KEY_DOWN || ev->type == SAPP_EVENTTYPE_KEY_UP) {
|
||||
float state = ev->type == SAPP_EVENTTYPE_KEY_DOWN ? 1.0 : 0.0;
|
||||
if (ev->key_code > 0 && ev->key_code < sizeof(keyboard_map)) {
|
||||
int code = keyboard_map[ev->key_code];
|
||||
input_set_button_state(code, state);
|
||||
}
|
||||
}
|
||||
|
||||
else if (ev->type == SAPP_EVENTTYPE_CHAR) {
|
||||
input_textinput(ev->char_code);
|
||||
}
|
||||
|
||||
|
||||
// Input Gamepad Axis
|
||||
// TODO: not implemented by sokol_app itself
|
||||
|
||||
// Mouse buttons
|
||||
else if (
|
||||
ev->type == SAPP_EVENTTYPE_MOUSE_DOWN ||
|
||||
ev->type == SAPP_EVENTTYPE_MOUSE_UP
|
||||
) {
|
||||
button_t button = INPUT_BUTTON_NONE;
|
||||
switch (ev->mouse_button) {
|
||||
case SAPP_MOUSEBUTTON_LEFT: button = INPUT_MOUSE_LEFT; break;
|
||||
case SAPP_MOUSEBUTTON_MIDDLE: button = INPUT_MOUSE_MIDDLE; break;
|
||||
case SAPP_MOUSEBUTTON_RIGHT: button = INPUT_MOUSE_RIGHT; break;
|
||||
default: break;
|
||||
}
|
||||
if (button != INPUT_BUTTON_NONE) {
|
||||
float state = ev->type == SAPP_EVENTTYPE_MOUSE_DOWN ? 1.0 : 0.0;
|
||||
input_set_button_state(button, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse wheel
|
||||
else if (ev->type == SAPP_EVENTTYPE_MOUSE_SCROLL) {
|
||||
button_t button = ev->scroll_y > 0
|
||||
? INPUT_MOUSE_WHEEL_UP
|
||||
: INPUT_MOUSE_WHEEL_DOWN;
|
||||
input_set_button_state(button, 1.0);
|
||||
input_set_button_state(button, 0.0);
|
||||
}
|
||||
|
||||
// Mouse move
|
||||
else if (ev->type == SAPP_EVENTTYPE_MOUSE_MOVE) {
|
||||
input_set_mouse_pos(ev->mouse_x, ev->mouse_y);
|
||||
}
|
||||
|
||||
// Window Events
|
||||
if (ev->type == SAPP_EVENTTYPE_RESIZED) {
|
||||
system_resize(vec2i(ev->window_width, ev->window_height));
|
||||
}
|
||||
}
|
||||
|
||||
void platform_audio_callback(float* buffer, int num_frames, int num_channels) {
|
||||
if (audio_callback) {
|
||||
audio_callback(buffer, num_frames * num_channels);
|
||||
}
|
||||
else {
|
||||
memset(buffer, 0, num_frames * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)) {
|
||||
audio_callback = cb;
|
||||
}
|
||||
|
||||
sapp_desc sokol_main(int argc, char* argv[]) {
|
||||
stm_setup();
|
||||
|
||||
saudio_setup(&(saudio_desc){
|
||||
.sample_rate = 44100,
|
||||
.buffer_frames = 4096,
|
||||
.num_packets = 256,
|
||||
.num_channels = 2,
|
||||
.stream_cb = platform_audio_callback,
|
||||
});
|
||||
|
||||
return (sapp_desc) {
|
||||
.width = SYSTEM_WINDOW_WIDTH,
|
||||
.height = SYSTEM_WINDOW_HEIGHT,
|
||||
.init_cb = system_init,
|
||||
.frame_cb = system_update,
|
||||
.cleanup_cb = system_cleanup,
|
||||
.event_cb = platform_handle_event,
|
||||
.win32_console_attach = true
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef RENDER_H
|
||||
#define RENDER_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef enum {
|
||||
RENDER_BLEND_NORMAL,
|
||||
RENDER_BLEND_LIGHTER
|
||||
} render_blend_mode_t;
|
||||
|
||||
#define RENDER_USE_MIPMAPS 1
|
||||
|
||||
#define RENDER_FADEOUT_NEAR 48000.0
|
||||
#define RENDER_FADEOUT_FAR 64000.0
|
||||
|
||||
extern uint16_t RENDER_NO_TEXTURE;
|
||||
|
||||
void render_init(vec2i_t size);
|
||||
void render_cleanup();
|
||||
|
||||
void render_resize(vec2i_t size);
|
||||
vec2i_t render_size();
|
||||
|
||||
void render_frame_prepare();
|
||||
void render_frame_end();
|
||||
|
||||
void render_set_view(vec3_t pos, vec3_t angles);
|
||||
void render_set_view_2d();
|
||||
void render_set_model_mat(mat4_t *m);
|
||||
void render_set_depth_write(bool enabled);
|
||||
void render_set_depth_test(bool enabled);
|
||||
void render_set_depth_offset(float offset);
|
||||
void render_set_screen_position(vec2_t pos);
|
||||
void render_set_blend_mode(render_blend_mode_t mode);
|
||||
void render_set_cull_backface(bool enabled);
|
||||
|
||||
vec3_t render_transform(vec3_t pos);
|
||||
void render_push_tris(tris_t tris, uint16_t texture);
|
||||
void render_push_sprite(vec3_t pos, vec2i_t size, rgba_t color, uint16_t texture);
|
||||
void render_push_2d(vec2i_t pos, vec2i_t size, rgba_t color, uint16_t texture);
|
||||
void render_push_2d_tile(vec2i_t pos, vec2i_t uv_offset, vec2i_t uv_size, vec2i_t size, rgba_t color, uint16_t texture_index);
|
||||
|
||||
uint16_t render_texture_create(uint32_t width, uint32_t height, rgba_t *pixels);
|
||||
vec2i_t render_texture_size(uint16_t texture_index);
|
||||
void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels);
|
||||
uint16_t render_textures_len();
|
||||
void render_textures_reset(uint16_t len);
|
||||
void render_textures_dump(const char *path);
|
||||
|
||||
#endif
|
||||
+719
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
#include "system.h"
|
||||
#include "input.h"
|
||||
#include "render.h"
|
||||
#include "platform.h"
|
||||
#include "mem.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "wipeout/game.h"
|
||||
|
||||
static double time_real;
|
||||
static double time_scaled;
|
||||
static double time_scale = 1.0;
|
||||
static double tick_last;
|
||||
static double cycle_time = 0;
|
||||
|
||||
void system_init() {
|
||||
time_real = platform_now();
|
||||
input_init();
|
||||
render_init(platform_screen_size());
|
||||
game_init();
|
||||
}
|
||||
|
||||
void system_cleanup() {
|
||||
render_cleanup();
|
||||
input_cleanup();
|
||||
}
|
||||
|
||||
void system_exit() {
|
||||
platform_exit();
|
||||
}
|
||||
|
||||
void system_update() {
|
||||
double time_real_now = platform_now();
|
||||
double real_delta = time_real_now - time_real;
|
||||
time_real = time_real_now;
|
||||
tick_last = min(real_delta, 0.1) * time_scale;
|
||||
time_scaled += tick_last;
|
||||
|
||||
// FIXME: come up with a better way to wrap the cycle_time, so that it
|
||||
// doesn't lose precission, but also doesn't jump upon reset.
|
||||
cycle_time = time_scaled;
|
||||
if (cycle_time > 3600 * M_PI) {
|
||||
cycle_time -= 3600 * M_PI;
|
||||
}
|
||||
|
||||
render_frame_prepare();
|
||||
|
||||
game_update();
|
||||
|
||||
render_frame_end();
|
||||
input_clear();
|
||||
mem_temp_check();
|
||||
}
|
||||
|
||||
void system_reset_cycle_time() {
|
||||
cycle_time = 0;
|
||||
}
|
||||
|
||||
void system_resize(vec2i_t size) {
|
||||
render_resize(size);
|
||||
}
|
||||
|
||||
double system_time_scale_get() {
|
||||
return time_scale;
|
||||
}
|
||||
|
||||
void system_time_scale_set(double scale) {
|
||||
time_scale = scale;
|
||||
}
|
||||
|
||||
double system_tick() {
|
||||
return tick_last;
|
||||
}
|
||||
|
||||
double system_time() {
|
||||
return time_scaled;
|
||||
}
|
||||
|
||||
double system_cycle_time() {
|
||||
return cycle_time;
|
||||
}
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#ifndef SYSTEM_H
|
||||
#define SYSTEM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define SYSTEM_WINDOW_NAME "wipEout"
|
||||
#define SYSTEM_WINDOW_WIDTH 1280
|
||||
#define SYSTEM_WINDOW_HEIGHT 720
|
||||
|
||||
void system_init();
|
||||
void system_update();
|
||||
void system_cleanup();
|
||||
void system_exit();
|
||||
void system_resize(vec2i_t size);
|
||||
|
||||
double system_time();
|
||||
double system_tick();
|
||||
double system_cycle_time();
|
||||
void system_reset_cycle_time();
|
||||
double system_time_scale_get();
|
||||
void system_time_scale_set(double ts);
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user