remove symlink as windows is shit, src to townengine

This commit is contained in:
veclavtalica
2024-07-30 22:31:18 +03:00
parent 222b68c0a3
commit e74cc6bf94
40 changed files with 14 additions and 15 deletions

View File

@ -1,346 +0,0 @@
#include "audio/internal_api.h"
#include "audio.h"
#include "config.h"
#include "context.h"
#include "util.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
#include <physfs.h>
#define STB_VORBIS_HEADER_ONLY
#include <stb_vorbis.c>
#include <stdint.h>
#include <string.h>
/* TODO: default to float sampling format? */
static const char *audio_exts[audio_file_type_count] = {
".ogg", /* audio_file_type_ogg */
".xm", /* audio_file_type_xm */
};
/* TODO: count frames without use, free the memory when threshold is met */
/* TODO: count repeated usages for sound effect cases with rendering to ram? */
/* stores path to data hash, useful for sound effects */
static struct audio_file_cache {
char *key;
struct audio_file_cache_value {
unsigned char *data;
size_t len;
} value;
} *audio_file_cache;
static int64_t get_audio_data(const char *path, unsigned char **data) {
const struct audio_file_cache *cache = shgetp_null(audio_file_cache, path);
if (!cache) {
unsigned char *file;
int64_t len = file_to_bytes(path, &file);
if (len == -1) {
CRY("Audio error", "Error reading file");
return -1;
}
const struct audio_file_cache_value value = { file, (size_t)len };
shput(audio_file_cache, path, value);
*data = file;
return len;
}
*data = cache->value.data;
return (int64_t)cache->value.len;
}
void play_audio(const char *path, const char *channel) {
const struct audio_channel_item *pair = shgetp_null(ctx.audio_channels, channel);
if (!pair)
play_audio_ex(path, channel, get_default_audio_args());
else
play_audio_ex(path, channel, pair->value.args);
}
static t_audio_file_type infer_audio_file_type(const char *path) {
size_t path_len = strlen(path);
for (int i = 0; i < audio_file_type_count; ++i) {
size_t ext_length = strlen(audio_exts[i]);
if (path_len <= ext_length)
continue;
if (strcmp(&path[path_len - ext_length], audio_exts[i]) == 0)
return (t_audio_file_type)i;
}
return audio_file_type_unknown;
}
/* TODO: error propagation and clearing of resources on partial success? */
/* or should we expect things to simply fail? */
static union audio_context init_audio_context(const char *path, t_audio_file_type type) {
switch (type) {
case audio_file_type_ogg: {
unsigned char *data;
int64_t len = get_audio_data(path, &data);
if (len == -1) {
CRY("Audio error", "Error reading file");
break;
}
int error = 0;
stb_vorbis* handle = stb_vorbis_open_memory(data, (int)len, &error, NULL);
if (error != 0) {
CRY("Audio error", "Error reading .ogg file");
break;
}
stb_vorbis_info info = stb_vorbis_get_info(handle);
return (union audio_context) {
.vorbis = {
.data = data,
.handle = handle,
.frequency = info.sample_rate,
.channel_count = (uint8_t)info.channels,
}
};
}
case audio_file_type_xm: {
unsigned char *data;
int64_t len = get_audio_data(path, &data);
if (len == -1) {
CRY("Audio error", "Error reading file");
break;
}
xm_context_t *handle;
int response = xm_create_context_safe(&handle,
(const char *)data,
(size_t)len,
AUDIO_FREQUENCY);
if (response != 0) {
CRY("Audio error", "Error loading xm module");
break;
}
xm_set_max_loop_count(handle, 1);
return (union audio_context) {
.xm = { .handle = handle }
};
}
default:
CRY("Audio error", "Unhandled audio format (in init)");
return (union audio_context){0};
}
return (union audio_context){0};
}
static void repeat_audio(struct audio_channel *channel) {
switch (channel->file_type) {
case audio_file_type_ogg: {
stb_vorbis_seek_start(channel->context.vorbis.handle);
break;
}
case audio_file_type_xm: {
xm_restart(channel->context.xm.handle);
break;
}
default:
CRY("Audio error", "Unhandled audio format (in repeat)");
break;
}
}
void play_audio_ex(const char *path, const char *channel, t_play_audio_args args) {
struct audio_channel_item *pair = shgetp_null(ctx.audio_channels, channel);
/* create a channel if it doesn't exist */
if (!pair) {
t_audio_file_type file_type = infer_audio_file_type(path);
struct audio_channel new_channel = {
.args = args,
.file_type = file_type,
.context = init_audio_context(path, file_type),
.path = path,
.name = channel,
};
shput(ctx.audio_channels, channel, new_channel);
pair = shgetp_null(ctx.audio_channels, channel);
}
/* TODO: destroy and create new context when channel is reused for different file */
/* works for both restarts and new audio */
if (strcmp(pair->value.path, path) == 0)
repeat_audio(&pair->value);
}
t_play_audio_args *get_audio_args(const char *channel) {
struct audio_channel_item *pair = shgetp_null(ctx.audio_channels, channel);
if (!pair)
return NULL;
return &pair->value.args;
}
t_play_audio_args get_default_audio_args(void) {
return (t_play_audio_args){
.repeat = false,
.crossfade = false,
.volume = 1.0f,
.panning = 0.0f,
};
}
/* this assumes int16_t based streams */
static void audio_mixin_streams(const struct audio_channel *channel,
uint8_t *restrict a,
uint8_t *restrict b,
size_t frames)
{
int16_t *const sa = (int16_t *)a;
int16_t *const sb = (int16_t *)b;
const float left_panning = fminf(fabsf(channel->args.panning - 1.0f), 1.0f);
const float right_panning = fminf(fabsf(channel->args.panning + 1.0f), 1.0f);
#if AUDIO_N_CHANNELS == 2
for (size_t s = 0; s < frames; s += 2) {
/* left channel */
sa[s] += (int16_t)(sb[s] * channel->args.volume * left_panning);
/* right channel */
sa[s + 1] += (int16_t)(sb[s + 1] * channel->args.volume * right_panning);
}
#else
#error "Unimplemented channel count"
#endif
}
/* remember: sample is data for all channels where frame is a part of it */
static void audio_sample_and_mixin_channel(const struct audio_channel *channel,
uint8_t *stream,
int len)
{
static uint8_t buffer[16384];
const int int16_buffer_frames = sizeof (buffer) / sizeof (int16_t);
const int float_buffer_frames = sizeof (buffer) / sizeof (float);
const int stream_frames = len / (int)(sizeof (int16_t));
switch (channel->file_type) {
case audio_file_type_ogg: {
/* feed stream for needed conversions */
for (int i = 0; i < stream_frames; ) {
const int n_frames = (stream_frames - i) > int16_buffer_frames ?
int16_buffer_frames : stream_frames - i;
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
channel->context.vorbis.handle,
channel->context.vorbis.channel_count,
(int16_t *)buffer,
n_frames);
/* handle end of file */
if (samples_per_channel == 0) {
if (channel->args.repeat) {
/* seek to start and try sampling some more */
stb_vorbis_seek_start(channel->context.vorbis.handle);
continue;
} else
/* leave silence */
break;
}
/* panning and mixing */
audio_mixin_streams(channel,
&stream[i * sizeof(int16_t)], buffer,
samples_per_channel * 2);
i += samples_per_channel * 2;
}
break;
}
case audio_file_type_xm: {
for (int i = 0; i < stream_frames; ) {
const int n_frames = (stream_frames - i) > float_buffer_frames ?
float_buffer_frames : stream_frames - i;
const int samples_per_channel = xm_generate_samples(channel->context.xm.handle,
(float *)buffer,
n_frames / 2);
/* handle end of file */
if (samples_per_channel == 0) {
if (channel->args.repeat) {
/* seek to start and try sampling some more */
xm_restart(channel->context.xm.handle);
continue;
} else
/* leave silence */
break;
}
/* convert floats to int16_t */
for (int p = 0; p < samples_per_channel * 2; ++p)
((int16_t *)buffer)[p] = (int16_t)(((float *)buffer)[p] * 32768.0f);
/* panning and mixing */
audio_mixin_streams(channel,
&stream[i * sizeof(int16_t)],
buffer,
samples_per_channel * 2);
i += samples_per_channel * 2;
}
break;
}
default:
CRY("Audio error", "Unhandled audio format (in sampling)");
break;
}
}
static void sanity_check_channel(const struct audio_channel *channel) {
if (channel->args.volume < 0.0f || channel->args.volume > 1.0f)
log_warn("Volume argument is out of range for channel (%s)", channel->name);
if (channel->args.panning < -1.0f || channel->args.panning > 1.0f)
log_warn("panning argument is out of range for channel (%s)", channel->name);
}
void audio_callback(void *userdata, uint8_t *stream, int len) {
(void)userdata;
/* prepare for mixing */
SDL_memset(stream, 0, len);
for (int i = 0; i < shlen(ctx.audio_channels); ++i) {
sanity_check_channel(&ctx.audio_channels[i].value);
audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len);
}
}

View File

@ -1,36 +0,0 @@
#ifndef AUDIO_H
#define AUDIO_H
#include <stdbool.h>
typedef struct play_audio_args {
/* default: false */
bool repeat;
/* crossfade between already playing audio on a given channel, if any */
/* default: false */
bool crossfade;
/* range: 0.0f to 1.0f */
/* default: 1.0f */
float volume;
/* range: -1.0 to 1.0f */
/* default: 0.0f */
float panning;
} t_play_audio_args;
/* plays audio file at specified channel or anywhere if NULL is passed */
/* path must contain valid file extension to infer which file format it is */
/* supported formats: .ogg, .xm */
/* preserves args that are already specified on the channel */
void play_audio(const char *path, const char *channel);
void play_audio_ex(const char *path, const char *channel, t_play_audio_args args);
/* could be used for modifying args */
/* warn: is only valid if no other calls to audio are made */
t_play_audio_args *get_audio_args(const char *channel);
t_play_audio_args get_default_audio_args(void);
#endif

View File

@ -1,55 +0,0 @@
#ifndef PRIVATE_AUDIO_H
#define PRIVATE_AUDIO_H
#include "../audio.h"
#include <SDL2/SDL_audio.h>
#define STB_VORBIS_HEADER_ONLY
#include <stb_vorbis.c>
#include <xm.h>
#include <stdbool.h>
#include <stdint.h>
typedef enum audio_file_type {
audio_file_type_ogg,
audio_file_type_xm,
audio_file_type_count,
audio_file_type_unknown,
} t_audio_file_type;
union audio_context {
struct {
stb_vorbis *handle;
unsigned char *data;
int frequency;
uint8_t channel_count;
} vorbis;
struct {
xm_context_t *handle;
} xm;
};
struct audio_channel {
t_play_audio_args args;
enum audio_file_type file_type;
union audio_context context; /* interpreted by `file_type` value */
const char *path;
const char *name;
};
struct audio_channel_item {
char *key;
struct audio_channel value;
};
void audio_callback(void *userdata, uint8_t *stream, int len);
#endif

View File

@ -1,52 +0,0 @@
#include "camera.h"
#include "context.h"
#include <math.h>
#define CAMERA_NEAR_Z 0.1f
#define CAMERA_FAR_Z 100.0f
t_matrix4 camera_look_at(const t_camera *const camera) {
/* from cglm */
const t_fvec3 r = m_vec_norm(m_vec_cross(camera->target, camera->up));
const t_fvec3 u = m_vec_cross(r, camera->target);
t_matrix4 result;
result.row[0].x = r.x;
result.row[0].y = u.x;
result.row[0].z = -camera->target.x;
result.row[1].x = r.y;
result.row[1].y = u.y;
result.row[1].z = -camera->target.y;
result.row[2].x = r.z;
result.row[2].y = u.z;
result.row[2].z = -camera->target.z;
result.row[3].x = -m_vec_dot(r, camera->pos);
result.row[3].y = -m_vec_dot(u, camera->pos);
result.row[3].z = m_vec_dot(camera->target, camera->pos);
result.row[0].w = result.row[1].w = result.row[2].w = 0.0f;
result.row[3].w = 1.0f;
return result;
}
t_matrix4 camera_perspective(const t_camera *const camera) {
/* from cglm */
t_matrix4 result = {0};
const float aspect = RENDER_BASE_RATIO;
const float f = 1.0f / tanf(camera->fov * 0.5f);
const float fn = 1.0f / (CAMERA_NEAR_Z - CAMERA_FAR_Z);
result.row[0].x = f / aspect;
result.row[1].y = f;
result.row[2].z = (CAMERA_NEAR_Z + CAMERA_FAR_Z) * fn;
result.row[2].w = -1.0f;
result.row[3].z = 2.0f * CAMERA_NEAR_Z * CAMERA_FAR_Z * fn;
return result;
}

View File

@ -1,21 +0,0 @@
#ifndef CAMERA_H
#define CAMERA_H
#include "util.h"
/* TODO: make it cached */
/* for example, perspective matrix only needs recaluclation on FOV change */
/* first person camera class */
typedef struct camera {
t_fvec3 pos; /* eye position */
t_fvec3 target; /* normalized target vector */
t_fvec3 up; /* normalized up vector */
float fov; /* field of view, in radians */
} t_camera;
t_matrix4 camera_look_at(const t_camera *camera);
t_matrix4 camera_perspective(const t_camera *const camera);
#endif

View File

@ -1,35 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
/*
* this file is for configuration values which are to be set at
* compile time. generally speaking, it's for things that would be unwise to
* change mid-development without considering the work it might take to
* adapt the game logic.
*
* if you're looking for distribution-related definitions like
* APP_NAME, you should know that they are set from CMake.
*/
#define TICKS_PER_SECOND 60
#define FIXED_DELTA_TIME (1.0 / TICKS_PER_SECOND)
#define RENDER_BASE_WIDTH 640
#define RENDER_BASE_HEIGHT 360
#define RENDER_BASE_RATIO ((float)RENDER_BASE_WIDTH / (float)RENDER_BASE_HEIGHT)
#define TEXTURE_ATLAS_SIZE 2048
#define TEXTURE_ATLAS_BIT_DEPTH 32
#define TEXTURE_ATLAS_FORMAT SDL_PIXELFORMAT_RGBA32
#define NUM_KEYBIND_SLOTS 8
#define AUDIO_FREQUENCY 48000
#define AUDIO_N_CHANNELS 2
/* 1024 * 1024 */
/* #define UMKA_STACK_SIZE 1048576 */
#endif

View File

@ -1,3 +0,0 @@
#include "context.h"
t_ctx ctx = {0};

View File

@ -1,67 +0,0 @@
#ifndef CONTEXT_H
#define CONTEXT_H
#include "textures/internal_api.h"
#include "input.h"
#include <SDL2/SDL.h>
#include <stdbool.h>
#include <stdint.h>
typedef struct context {
/* the program's actual argc and argv */
int argc;
char **argv;
struct texture_cache texture_cache;
struct input_state input;
struct primitive_2d *render_queue_2d;
struct mesh_batch_item *uncolored_mesh_batches;
struct audio_channel_item *audio_channels;
SDL_AudioDeviceID audio_device;
int audio_stream_frequency;
SDL_AudioFormat audio_stream_format;
uint8_t audio_stream_channel_count;
/* main loop machinery */
int64_t clocks_per_second;
int64_t prev_frame_time;
int64_t desired_frametime; /* how long one tick should be */
int64_t frame_accumulator;
int64_t delta_averager_residual;
int64_t time_averager[4];
int64_t delta_time; /* preserves real time frame delta with no manipilation */
uint64_t tick_count;
/* set just once on startup */
uint64_t random_seed;
/* this should be a multiple of TICKS_PER_SECOND */
/* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
/* it can be changed at runtime; any resulting logic anomalies are bugs */
unsigned int update_multiplicity;
SDL_GLContext *gl_context;
SDL_Window *window;
uint32_t window_id;
int window_w;
int window_h;
/* you may read from and write to these from game code */
void *udata;
bool debug;
bool is_running;
bool resync_flag;
bool was_successful;
bool window_size_has_changed;
} t_ctx;
extern t_ctx ctx;
#endif

View File

@ -1,16 +0,0 @@
/* include this header in game code to get the usable parts of the engine */
#ifndef GAME_API_H
#define GAME_API_H
#include "context.h"
#include "rendering.h"
#include "audio.h"
#include "util.h"
#include "procgen.h"
/* application provided */
extern void game_tick(void);
extern void game_end(void);
#endif

View File

@ -1,313 +0,0 @@
#include "input.h"
#include "util.h"
#include "context.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
#include <stdbool.h>
#include <stdlib.h>
static void update_action_pressed_state(struct input_state *input, struct action *action) {
for (size_t i = 0; i < SDL_arraysize(action->bindings); ++i) {
switch (action->bindings[i].source) {
case BUTTON_SOURCE_NOT_SET:
break;
case BUTTON_SOURCE_KEYBOARD_PHYSICAL:
/* not pressed */
if (input->keyboard_state[action->bindings[i].code.scancode] == 0) {
action->just_changed = action->is_pressed;
action->is_pressed = false;
}
/* pressed */
else {
action->just_changed = !action->is_pressed;
action->is_pressed = true;
return;
}
break;
case BUTTON_SOURCE_MOUSE:
/* not pressed */
if ((input->mouse_state & action->bindings[i].code.mouse_button) == 0) {
action->just_changed = action->is_pressed;
action->is_pressed = false;
}
/* pressed */
else {
action->just_changed = !action->is_pressed;
action->is_pressed = true;
action->position.x = (float)input->mouse_window_position.x;
action->position.x = (float)input->mouse_window_position.x;
/* TODO: */
/*
* SDL_RenderWindowToLogical will turn window mouse
* coords into a position inside the logical render
* area. this has to be done to get an accurate point
* that can actually be used in game logic
*/
// SDL_RenderWindowToLogical(input->renderer,
// input->mouse_window_position.x,
// input->mouse_window_position.y,
// &action->position.x,
// &action->position.y);
return;
}
break;
default:
break;
}
}
}
static void input_bind_code_to_action(struct input_state *input,
char *action_name,
enum button_source source,
union button_code code)
{
struct action_hash_item *action_item = shgetp_null(input->action_hash, action_name);
if (action_item == NULL) {
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
return;
}
struct action *action = &action_item->value;
/* check every binding to make sure this code isn't already bound */
for (size_t i = 0; i < SDL_arraysize(action->bindings); ++i) {
struct button *binding = &action->bindings[i];
if (binding->source != source)
break;
bool is_already_bound = false;
switch (binding->source) {
case BUTTON_SOURCE_NOT_SET:
break;
case BUTTON_SOURCE_KEYBOARD_PHYSICAL:
is_already_bound = binding->code.scancode == code.scancode;
break;
case BUTTON_SOURCE_KEYBOARD_CHARACTER:
is_already_bound = binding->code.keycode == code.keycode;
break;
case BUTTON_SOURCE_GAMEPAD:
is_already_bound = binding->code.gamepad_button == code.gamepad_button;
break;
case BUTTON_SOURCE_MOUSE:
is_already_bound = binding->code.mouse_button == code.mouse_button;
break;
}
if (is_already_bound) {
log_warn("(%s) Code already bound to action \"%s\".", __func__, action_name);
return;
}
}
/* if we're at max bindings, forget the first element and shift the rest */
if (action->num_bindings == SDL_arraysize(action->bindings)) {
--action->num_bindings;
size_t shifted_size = (sizeof action->bindings) - (sizeof action->bindings[0]);
memmove(action->bindings, action->bindings + 1, shifted_size);
}
action->bindings[action->num_bindings++] = (struct button) {
.source = source,
.code = code,
};
}
static void input_unbind_code_from_action(struct input_state *input,
char *action_name,
enum button_source source,
union button_code code)
{
struct action_hash_item *action_item = shgetp_null(input->action_hash, action_name);
if (action_item == NULL) {
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
return;
}
struct action *action = &action_item->value;
/* check every binding to make sure this code is bound */
size_t index = 0;
bool is_bound = false;
for (index = 0; index < SDL_arraysize(action->bindings); ++index) {
struct button *binding = &action->bindings[index];
if (binding->source != source)
continue;
switch (binding->source) {
case BUTTON_SOURCE_NOT_SET:
break;
case BUTTON_SOURCE_KEYBOARD_PHYSICAL:
is_bound = binding->code.scancode == code.scancode;
break;
case BUTTON_SOURCE_KEYBOARD_CHARACTER:
is_bound = binding->code.keycode == code.keycode;
break;
case BUTTON_SOURCE_GAMEPAD:
is_bound = binding->code.gamepad_button == code.gamepad_button;
break;
case BUTTON_SOURCE_MOUSE:
is_bound = binding->code.mouse_button == code.mouse_button;
break;
}
if (is_bound)
break;
}
if (!is_bound) {
log_warn("(%s) Code is not bound to action \"%s\".", __func__, action_name);
return;
}
/* remove the element to unbind and shift the rest so there isn't a gap */
if (action->num_bindings == SDL_arraysize(action->bindings)) {
size_t elements_after_index = action->num_bindings - index;
size_t shifted_size = elements_after_index * (sizeof action->bindings[0]);
memmove(action->bindings + index, action->bindings + index + 1, shifted_size);
--action->num_bindings;
}
}
void input_state_init(struct input_state *input) {
sh_new_strdup(input->action_hash);
}
void input_state_deinit(struct input_state *input) {
shfree(input->action_hash);
}
void input_state_update(struct input_state *input) {
input->keyboard_state = SDL_GetKeyboardState(NULL);
input->mouse_state = SDL_GetMouseState(&input->mouse_window_position.x,
&input->mouse_window_position.y);
SDL_GetRelativeMouseState(&input->mouse_relative_position.x,
&input->mouse_relative_position.y);
for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
struct action *action = &input->action_hash[i].value;
update_action_pressed_state(input, action);
}
}
void input_bind_action_scancode(struct input_state *input,
char *action_name,
SDL_Scancode scancode)
{
input_bind_code_to_action(input,
action_name,
BUTTON_SOURCE_KEYBOARD_PHYSICAL,
(union button_code) { .scancode = scancode });
}
void input_unbind_action_scancode(struct input_state *input,
char *action_name,
SDL_Scancode scancode)
{
input_unbind_code_from_action(input,
action_name,
BUTTON_SOURCE_KEYBOARD_PHYSICAL,
(union button_code) { .scancode = scancode });
}
void input_bind_action_mouse(struct input_state *input,
char *action_name,
uint8_t mouse_button)
{
input_bind_code_to_action(input,
action_name,
BUTTON_SOURCE_MOUSE,
(union button_code) { .mouse_button = mouse_button});
}
void input_unbind_action_mouse(struct input_state *input,
char *action_name,
uint8_t mouse_button)
{
input_unbind_code_from_action(input,
action_name,
BUTTON_SOURCE_MOUSE,
(union button_code) { .mouse_button = mouse_button});
}
void input_add_action(struct input_state *input, char *action_name) {
if (shgeti(input->action_hash, action_name) >= 0) {
log_warn("(%s) Action \"%s\" is already registered.", __func__, action_name);
return;
}
shput(input->action_hash, action_name, (struct action) { 0 });
}
void input_delete_action(struct input_state *input, char *action_name) {
if (shdel(input->action_hash, action_name) == 0)
log_warn("(%s) Action \"%s\" is not registered.", __func__, action_name);
}
bool input_is_action_pressed(struct input_state *input, char *action_name) {
struct action_hash_item *action = shgetp_null(input->action_hash, action_name);
if (action == NULL) {
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
return false;
}
return action->value.is_pressed;
}
bool input_is_action_just_pressed(struct input_state *input, char *action_name) {
struct action_hash_item *action = shgetp_null(input->action_hash, action_name);
if (action == NULL) {
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
return false;
}
return action->value.is_pressed && action->value.just_changed;
}
bool input_is_action_just_released(struct input_state *input, char *action_name) {
struct action_hash_item *action = shgetp_null(input->action_hash, action_name);
if (action == NULL) {
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
return false;
}
return !action->value.is_pressed && action->value.just_changed;
}
t_fvec2 input_get_action_position(struct input_state *input, char *action_name) {
struct action_hash_item *action = shgetp_null(input->action_hash, action_name);
if (action == NULL) {
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
return (t_fvec2) { 0 };
}
return action->value.position;
}
void input_set_mouse_captured(struct input_state *input, bool enabled) {
/* TODO: returns -1 if not supported, but like... do we care? */
SDL_SetRelativeMouseMode(enabled);
}
bool input_is_mouse_captured(struct input_state *input) {
return SDL_GetRelativeMouseMode();
}

View File

@ -1,100 +0,0 @@
#ifndef INPUT_H
#define INPUT_H
#include "config.h"
#include "src/vec.h"
#include "util.h"
#include <SDL2/SDL.h>
#include <stdint.h>
#include <stdbool.h>
enum button_source {
BUTTON_SOURCE_NOT_SET,
BUTTON_SOURCE_KEYBOARD_PHYSICAL,
BUTTON_SOURCE_KEYBOARD_CHARACTER,
BUTTON_SOURCE_GAMEPAD,
BUTTON_SOURCE_MOUSE,
};
union button_code {
SDL_Scancode scancode;
SDL_Keycode keycode;
SDL_GameControllerButton gamepad_button;
uint8_t mouse_button; /* SDL_BUTTON_ enum */
};
/* an input to which an action is bound */
/* it is not limited to literal buttons */
struct button {
enum button_source source;
union button_code code;
};
/* represents the collective state of a group of buttons */
/* that is, changes in the states of any of the bound buttons will affect it */
struct action {
size_t num_bindings;
/* if you bind more than NUM_KEYBIND_SLOTS (set in config.h) */
/* it forgets the first button to add the new one at the end */
struct button bindings[NUM_KEYBIND_SLOTS];
t_fvec2 position; /* set if applicable, e.g. mouse click */
bool is_pressed;
bool just_changed;
};
struct action_hash_item {
char *key;
struct action value;
};
struct input_state {
struct action_hash_item *action_hash;
const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
uint32_t mouse_state; /* SDL mouse button bitmask */
t_vec2 mouse_window_position;
t_vec2 mouse_relative_position;
enum button_source last_active_source;
bool is_anything_just_pressed;
};
void input_state_init(struct input_state *input);
void input_state_deinit(struct input_state *input);
void input_state_update(struct input_state *input);
void input_bind_action_scancode(struct input_state *input,
char *action_name,
SDL_Scancode scancode);
void input_unbind_action_scancode(struct input_state *input,
char *action_name,
SDL_Scancode scancode);
void input_bind_action_mouse(struct input_state *input,
char *action_name,
uint8_t mouse_button);
void input_unbind_action_mouse(struct input_state *input,
char *action_name,
uint8_t mouse_button);
void input_add_action(struct input_state *input, char *action_name);
void input_delete_action(struct input_state *input, char *action_name);
bool input_is_action_pressed(struct input_state *input, char *action_name);
bool input_is_action_just_pressed(struct input_state *input, char *action_name);
bool input_is_action_just_released(struct input_state *input, char *action_name);
t_fvec2 input_get_action_position(struct input_state *input, char *action_name);
void input_set_mouse_captured(struct input_state *input, bool value);
bool input_is_mouse_captured(struct input_state *input);
#endif

View File

@ -1,8 +0,0 @@
#ifndef CONCATENATE_H
#define CONCATENATE_H
#define m_concatenate(p_a, p_b) m_concatenate_(p_a, p_b)
#define m_concatenate_(p_a, p_b) m_concatenate__(p_a, p_b)
#define m_concatenate__(p_a, p_b) p_a##p_b
#endif

View File

@ -1,65 +0,0 @@
#ifndef OPTION_H
#define OPTION_H
#include "concatenate.h"
#include "varargcount.h"
#include <stdbool.h>
/* usage example:
*
* struct result {
* float f;
* m_option_list(
* int, v
* )
* }
*
* struct result = {
* m_set(f, 1.0), -- non options could be set with this
* m_opt(v, 5), -- options are supposed to be initialized like that
* }
*
*/
#define m_set(p_member, p_value) .p_member = (p_value)
#define m_opt(p_member, p_value) .p_member##_opt = (p_value), .p_member##_opt_set = 1
#define m_is_set(p_value, p_member) ((p_value).p_member##_opt_set)
/* warn: beware of double evaluation! */
#define m_or(p_value, p_member, p_default) ((p_value).p_member##_opt_set ? (p_value).p_member##_opt : (p_default))
#define m_option_list_2(t0, m0) \
t0 m0##_opt; \
bool m0##_opt_set : 1; \
#define m_option_list_4(t0, m0, t1, m1) \
t0 m0##_opt; \
t1 m1##_opt; \
bool m0##_opt_set : 1; \
bool m1##_opt_set : 1; \
#define m_option_list_6(t0, m0, t1, m1, t2, m2) \
t0 m0##_opt; \
t1 m1##_opt; \
t2 m1##_opt; \
bool m0##_opt_set : 1; \
bool m1##_opt_set : 1; \
bool m2##_opt_set : 1; \
#define m_option_list_8(t0, m0, t1, m1, t2, m2, t3, m3) \
t0 m0##_opt; \
t1 m1##_opt; \
t2 m2##_opt; \
t3 m3##_opt; \
bool m0##_opt_set : 1; \
bool m1##_opt_set : 1; \
bool m2##_opt_set : 1; \
bool m3##_opt_set : 1; \
#define m_option_list_(p_n, ...) m_concatenate(m_option_list_, p_n)(__VA_ARGS__)
#define m_option_list(...) m_option_list_(m_narg(__VA_ARGS__), __VA_ARGS__)
#endif

View File

@ -1,9 +0,0 @@
#ifndef VARARGCOUNT_H
#define VARARGCOUNT_H
#define m_narg(...) m_narg_(__VA_ARGS__, m_rseq_n_())
#define m_narg_(...) m_arg_n_(__VA_ARGS__)
#define m_arg_n_(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define m_rseq_n_() 8, 7, 6, 5, 4, 3, 2, 1, 0
#endif

View File

@ -1,388 +0,0 @@
#include "context.h"
#include "rendering.h"
#include "input.h"
#include "util.h"
#include "game_api.h"
#include "audio/internal_api.h"
#include "textures/internal_api.h"
#include "rendering/internal_api.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_image.h>
#include <physfs.h>
#include <stb_ds.h>
#include <glad/glad.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <tgmath.h>
#include <limits.h>
static void poll_events(void) {
SDL_Event e;
ctx.window_size_has_changed = false;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT:
ctx.is_running = false;
break;
case SDL_WINDOWEVENT:
if (e.window.windowID != ctx.window_id)
break;
switch (e.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
ctx.window_size_has_changed = true;
break;
}
break;
}
}
}
static void APIENTRY opengl_log(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam)
{
(void)source;
(void)type;
(void)id;
(void)severity;
(void)userParam;
log_info("OpenGL: %.*s\n", length, message);
}
static void main_loop(void) {
/*
if (!ctx.is_running) {
end(ctx);
clean_up(ctx);
emscripten_cancel_main_loop();
}
*/
/* frame timer */
int64_t current_frame_time = SDL_GetPerformanceCounter();
int64_t delta_time = current_frame_time - ctx.prev_frame_time;
ctx.prev_frame_time = current_frame_time;
ctx.delta_time = delta_time;
/* handle unexpected timer anomalies (overflow, extra slow frames, etc) */
if (delta_time > ctx.desired_frametime * 8) { /* ignore extra-slow frames */
delta_time = ctx.desired_frametime;
}
delta_time = MAX(0, delta_time);
/* vsync time snapping */
/* get the refresh rate of the current display (it might not always be the same one) */
int display_framerate = 60; /* a reasonable guess */
SDL_DisplayMode current_display_mode;
int current_display_index = SDL_GetWindowDisplayIndex(ctx.window);
if (SDL_GetCurrentDisplayMode(current_display_index, &current_display_mode) == 0)
display_framerate = current_display_mode.refresh_rate;
int64_t snap_hz = display_framerate;
if (snap_hz <= 0)
snap_hz = 60;
/* these are to snap delta time to vsync values if it's close enough */
int64_t vsync_maxerror = (int64_t)((double)ctx.clocks_per_second * 0.0002);
size_t snap_frequency_count = 8;
for (size_t i = 0; i < snap_frequency_count; ++i) {
int64_t frequency = (ctx.clocks_per_second / snap_hz) * (i+1);
if (llabs(delta_time - frequency) < vsync_maxerror) {
delta_time = frequency;
break;
}
}
/* delta time averaging */
size_t time_averager_count = SDL_arraysize(ctx.time_averager);
for (size_t i = 0; i < time_averager_count - 1; ++i) {
ctx.time_averager[i] = ctx.time_averager[i+1];
}
ctx.time_averager[time_averager_count - 1] = delta_time;
int64_t averager_sum = 0;
for (size_t i = 0; i < time_averager_count; ++i) {
averager_sum += ctx.time_averager[i];
}
delta_time = averager_sum / time_averager_count;
ctx.delta_averager_residual += averager_sum % time_averager_count;
delta_time += ctx.delta_averager_residual / time_averager_count;
ctx.delta_averager_residual %= time_averager_count;
/* add to the accumulator */
ctx.frame_accumulator += delta_time;
/* spiral of death protection */
if (ctx.frame_accumulator > ctx.desired_frametime * 8) {
ctx.resync_flag = true;
}
/* timer resync if requested */
if (ctx.resync_flag) {
ctx.frame_accumulator = 0;
delta_time = ctx.desired_frametime;
ctx.resync_flag = false;
}
/* finally, let's get to work */
while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) {
for (size_t i = 0; i < ctx.update_multiplicity; ++i) {
render_queue_clear();
poll_events();
if (ctx.window_size_has_changed) {
t_vec2 size;
SDL_GetWindowSize(ctx.window, &size.x, &size.y);
ctx.window_w = size.x;
ctx.window_h = size.y;
}
input_state_update(&ctx.input);
game_tick();
ctx.frame_accumulator -= ctx.desired_frametime;
ctx.tick_count = (ctx.tick_count % ULLONG_MAX) + 1;
render();
}
}
}
static bool initialize(void) {
if (SDL_Init(SDL_INIT_EVERYTHING) == -1) {
CRY_SDL("SDL initialization failed.");
return false;
}
/* TODO: recognize cli parameter to turn it on on release */
/* debug mode _defaults_ to being enabled on debug builds. */
/* you should be able to enable it at runtime on any build */
#ifndef NDEBUG
ctx.debug = true;
#else
ctx.debug = false;
#endif
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
if (ctx.debug)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_NO_ERROR);
/* init got far enough to create a window */
ctx.window = SDL_CreateWindow("townengine",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
RENDER_BASE_WIDTH,
RENDER_BASE_HEIGHT,
// SDL_WINDOW_ALLOW_HIGHDPI |
SDL_WINDOW_RESIZABLE |
SDL_WINDOW_OPENGL);
if (ctx.window == NULL) {
CRY_SDL("Window creation failed.");
goto fail;
}
ctx.gl_context = SDL_GL_CreateContext(ctx.window);
if (!ctx.gl_context) {
CRY_SDL("GL context creation failed.");
goto fail;
}
if (SDL_GL_MakeCurrent(ctx.window, ctx.gl_context)) {
CRY_SDL("GL context binding failed.");
goto fail;
}
if (SDL_GL_SetSwapInterval(-1))
SDL_GL_SetSwapInterval(1);
if (gladLoadGL() == 0) {
CRY("Init", "GLAD failed");
goto fail;
}
log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
glHint(GL_FOG_HINT, GL_FASTEST);
/* might need this to have multiple windows */
ctx.window_id = SDL_GetWindowID(ctx.window);
glViewport(0, 0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT);
/* TODO: */
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
ctx.window_w = RENDER_BASE_WIDTH;
ctx.window_h = RENDER_BASE_HEIGHT;
/* audio initialization */
{
SDL_AudioSpec request, got;
SDL_zero(request);
request.freq = AUDIO_FREQUENCY;
request.format = AUDIO_S16;
request.channels = AUDIO_N_CHANNELS;
request.callback = audio_callback;
/* TODO: check for errors */
ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0);
ctx.audio_stream_format = got.format;
ctx.audio_stream_frequency = got.freq;
ctx.audio_stream_channel_count = got.channels;
SDL_PauseAudioDevice(ctx.audio_device, 0);
}
/* images */
if (IMG_Init(IMG_INIT_PNG) == 0) {
CRY_SDL("SDL_image initialization failed.");
goto fail;
}
/* filesystem time */
/* TODO: ANDROID: see the warning in physicsfs PHYSFS_init docs/header */
if (!PHYSFS_init(ctx.argv[0]) ||
!PHYSFS_setSaneConfig(ORGANIZATION_NAME, APP_NAME, PACKAGE_EXTENSION, false, true))
{
CRY_PHYSFS("Filesystem initialization failed.");
goto fail;
}
/* you could change this at runtime if you wanted */
ctx.update_multiplicity = 1;
/* hook up opengl debugging callback */
if (ctx.debug) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(opengl_log, NULL);
}
/* random seeding */
/* SDL_GetPerformanceCounter returns some platform-dependent number. */
/* it should vary between game instances. i checked! random enough for me. */
ctx.random_seed = SDL_GetPerformanceCounter();
srand((unsigned int)ctx.random_seed);
stbds_rand_seed(ctx.random_seed);
/* main loop machinery */
ctx.is_running = true;
ctx.resync_flag = true;
ctx.clocks_per_second = SDL_GetPerformanceFrequency();
ctx.prev_frame_time = SDL_GetPerformanceCounter();
ctx.desired_frametime = ctx.clocks_per_second / TICKS_PER_SECOND;
ctx.frame_accumulator = 0;
ctx.tick_count = 0;
/* delta time averaging */
ctx.delta_averager_residual = 0;
for (size_t i = 0; i < SDL_arraysize(ctx.time_averager); ++i) {
ctx.time_averager[i] = ctx.desired_frametime;
}
/* rendering */
/* these are dynamic arrays and will be allocated lazily by stb_ds */
ctx.render_queue_2d = NULL;
textures_cache_init(&ctx.texture_cache, ctx.window);
if (TTF_Init() < 0) {
CRY_SDL("SDL_ttf initialization failed.");
goto fail;
}
/* input */
input_state_init(&ctx.input);
/* scripting */
/*
if (!scripting_init(ctx)) {
goto fail;
}
*/
return true;
fail:
SDL_Quit();
return false;
}
/* will not be called on an abnormal exit */
static void clean_up(void) {
/*
scripting_deinit(ctx);
*/
input_state_deinit(&ctx.input);
arrfree(ctx.render_queue_2d);
textures_cache_deinit(&ctx.texture_cache);
PHYSFS_deinit();
TTF_Quit();
IMG_Quit();
SDL_Quit();
}
int main(int argc, char **argv) {
ctx.argc = argc;
ctx.argv = argv;
if (!initialize())
return EXIT_FAILURE;
for (int i = 1; i < (argc - 1); ++i) {
if (strcmp(argv[i], "--data-dir") == 0) {
if (!PHYSFS_mount(argv[i+1], NULL, true)) {
CRY_PHYSFS("Data dir mount override failed.");
return EXIT_FAILURE;
}
}
}
ctx.was_successful = true;
while (ctx.is_running)
main_loop();
game_end();
clean_up();
return ctx.was_successful ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -1,6 +0,0 @@
#ifndef PROCGEN_H
#define PROCGEN_H
#include "procgen/perlin.h"
#endif

View File

@ -1,10 +0,0 @@
#ifndef PROCGEN_PERLIN_H
#define PROCGEN_PERLIN_H
#include "../vec.h"
void set_perlin_2d_seed(uint32_t seed);
float sample_perlin_2d(t_fvec2 position, float frequency, uint8_t octaves);
#endif

View File

@ -1,70 +0,0 @@
#include "perlin.h"
#include "../vec.h"
static uint32_t seed = 0;
static const uint8_t hash[] = { 208,34,231,213,32,248,233,56,161,78,24,140,71,48,140,254,245,255,247,247,40,
185,248,251,245,28,124,204,204,76,36,1,107,28,234,163,202,224,245,128,167,204,
9,92,217,54,239,174,173,102,193,189,190,121,100,108,167,44,43,77,180,204,8,81,
70,223,11,38,24,254,210,210,177,32,81,195,243,125,8,169,112,32,97,53,195,13,
203,9,47,104,125,117,114,124,165,203,181,235,193,206,70,180,174,0,167,181,41,
164,30,116,127,198,245,146,87,224,149,206,57,4,192,210,65,210,129,240,178,105,
228,108,245,148,140,40,35,195,38,58,65,207,215,253,65,85,208,76,62,3,237,55,89,
232,50,217,64,244,157,199,121,252,90,17,212,203,149,152,140,187,234,177,73,174,
193,100,192,143,97,53,145,135,19,103,13,90,135,151,199,91,239,247,33,39,145,
101,120,99,3,186,86,99,41,237,203,111,79,220,135,158,42,30,154,120,67,87,167,
135,176,183,191,253,115,184,21,233,58,129,233,142,39,128,211,118,137,139,255,
114,20,218,113,154,27,127,246,250,1,8,198,250,209,92,222,173,21,88,102,219 };
void set_perlin_2d_seed(uint32_t s) {
seed = s;
}
static int32_t noise2(int x, int y)
{
int tmp = hash[(y + seed) % 256];
return hash[(tmp + x) % 256];
}
static float lin_inter(float x, float y, float s)
{
return x + s * (y-x);
}
static float smooth_inter(float x, float y, float s)
{
return lin_inter(x, y, s * s * (3-2*s));
}
static float noise2d(float x, float y)
{
int32_t x_int = (int32_t)x;
int32_t y_int = (int32_t)y;
float x_frac = x - (float)x_int;
float y_frac = y - (float)y_int;
int32_t s = noise2(x_int, y_int);
int32_t t = noise2(x_int + 1, y_int);
int32_t u = noise2(x_int, y_int + 1);
int v = noise2(x_int + 1, y_int + 1);
float low = smooth_inter((float)s, (float)t, x_frac);
float high = smooth_inter((float)u, (float)v, x_frac);
return smooth_inter(low, high, y_frac);
}
/* https://gist.github.com/nowl/828013 */
float sample_perlin_2d(t_fvec2 position, float frequency, uint8_t octaves) {
t_fvec2 a = m_vec_scale(position, frequency);
float amp = 1.0;
float fin = 0;
float div = 0.0;
for(uint8_t i = 0; i < octaves; i++)
{
div += 256 * amp;
fin += noise2d(a.x, a.y) * amp;
amp /= 2;
a = m_vec_scale(a, 2);
}
return fin/div;
}

View File

@ -1,212 +0,0 @@
#include "rendering/internal_api.h"
#include "rendering/sprites.h"
#include "rendering/triangles.h"
#include "rendering/circles.h"
#include "textures/internal_api.h"
#include "context.h"
#include <SDL2/SDL.h>
#include <glad/glad.h>
#include <stb_ds.h>
#include <stddef.h>
#include <tgmath.h>
/* TODO: have a default initialized one */
static t_matrix4 camera_projection_matrix;
static t_matrix4 camera_look_at_matrix;
void render_queue_clear(void) {
/* since i don't intend to free the queues, */
/* it's faster and simpler to just "start over" */
/* and start overwriting the existing data */
arrsetlen(ctx.render_queue_2d, 0);
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
}
/* rectangle */
void push_rectangle(t_frect rect, t_color color) {
struct rect_primitive rectangle = {
.rect = rect,
.color = color,
};
struct primitive_2d primitive = {
.type = PRIMITIVE_2D_RECT,
.rect = rectangle,
};
arrput(ctx.render_queue_2d, primitive);
}
static void upload_quad_vertices(t_frect rect) {
/* client memory needs to be reachable on glDraw*, so */
static float vertices[6 * 2];
vertices[0] = rect.x; vertices[1] = rect.y;
vertices[2] = rect.x; vertices[3] = rect.y + rect.h;
vertices[4] = rect.x + rect.w; vertices[5] = rect.y + rect.h;
vertices[6] = rect.x + rect.w; vertices[7] = rect.y + rect.h;
vertices[8] = rect.x + rect.w; vertices[9] = rect.y;
vertices[10] = rect.x; vertices[11] = rect.y;
glVertexPointer(2, GL_FLOAT, 0, (void *)&vertices);
}
static void render_rectangle(const struct rect_primitive *rectangle) {
glColor4ub(rectangle->color.r, rectangle->color.g,
rectangle->color.b, rectangle->color.a);
glEnableClientState(GL_VERTEX_ARRAY);
upload_quad_vertices(rectangle->rect);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableClientState(GL_VERTEX_ARRAY);
}
static void render_2d(void) {
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
size_t batch_count = 0;
for (size_t i = 0; i < render_queue_len; ++i) {
const struct primitive_2d *current = &ctx.render_queue_2d[i];
switch (current->type) {
case PRIMITIVE_2D_SPRITE: {
const struct sprite_batch batch =
collect_sprite_batch(current, render_queue_len - i);
glDepthRange((double)batch_count / UINT16_MAX, 1.0);
render_sprites(current, batch);
i += batch.size - 1; ++batch_count;
break;
}
case PRIMITIVE_2D_RECT:
render_rectangle(&current->rect);
break;
case PRIMITIVE_2D_CIRCLE:
render_circle(&current->circle);
break;
}
}
}
static void render_space(void) {
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
glDepthRange(0, 1);
glDisable(GL_BLEND);
glEnable(GL_ALPHA_TEST); /* TODO: infer its usage */
glAlphaFunc(GL_EQUAL, 1.0f);
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
/* solid white, no modulation */
glColor4ub(255, 255, 255, 255);
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
ctx.uncolored_mesh_batches[i].key);
}
}
void render(void) {
textures_update_atlas(&ctx.texture_cache);
/* fit rendering context onto the resizable screen */
if (ctx.window_size_has_changed) {
if ((float)ctx.window_w / (float)ctx.window_h > RENDER_BASE_RATIO) {
float ratio = (float)ctx.window_h / (float)RENDER_BASE_HEIGHT;
int w = (int)((float)RENDER_BASE_WIDTH * ratio);
glViewport(
ctx.window_w / 2 - w / 2,
0,
w,
ctx.window_h
);
} else {
float ratio = (float)ctx.window_w / (float)RENDER_BASE_WIDTH;
int h = (int)((float)RENDER_BASE_HEIGHT * ratio);
glViewport(
0,
ctx.window_h / 2 - h / 2,
ctx.window_w,
h
);
}
}
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_SMOOTH);
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
{
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&camera_projection_matrix.row[0].x);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&camera_look_at_matrix.row[0].x);
render_space();
}
/* TODO: only do it when transition between spaces is needed */
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glFlush();
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glShadeModel(GL_FLAT);
/* removes near/far plane comparison and discard */
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
render_2d();
}
SDL_GL_SwapWindow(ctx.window);
}
void set_camera(const t_camera *const camera) {
/* TODO: skip recaulculating if it's the same? */
camera_projection_matrix = camera_perspective(camera);
camera_look_at_matrix = camera_look_at(camera);
}

View File

@ -1,68 +0,0 @@
#ifndef RENDERING_H
#define RENDERING_H
#include "util.h"
#include "macros/option.h"
#include "camera.h"
#include <SDL2/SDL.h>
#include <stdbool.h>
typedef struct push_sprite_args {
char *path;
t_frect rect;
m_option_list(
t_color, color,
float, rotation,
bool, flip_x,
bool, flip_y )
} t_push_sprite_args;
/* pushes a sprite onto the sprite render queue */
/* this is a simplified version of push_sprite_ex for the most common case. */
/* it assumes you want no color modulation, no rotation, no flip */
void push_sprite(t_push_sprite_args args);
#define m_sprite(...) (push_sprite((t_push_sprite_args){__VA_ARGS__}))
/* pushes a filled rectangle onto the rectangle render queue */
void push_rectangle(t_frect rect, t_color color);
/* pushes a filled circle onto the circle render queue */
void push_circle(t_fvec2 position, float radius, t_color color);
/* pushes a textured 3d triangle onto the render queue */
/* vertices are in absolute coordinates, relative to world origin */
/* texture coordinates are in pixels */
void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2);
/* pushes a colored textured 3d triangle onto the render queue */
// void unfurl_colored_triangle(const char *path,
// t_fvec3 v0,
// t_fvec3 v1,
// t_fvec3 v2,
// t_shvec2 uv0,
// t_shvec2 uv1,
// t_shvec2 uv2,
// t_color c0,
// t_color c1,
// t_color c2);
// TODO:
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2
// void unfurl_billboard(const char *path,
// t_fvec3 position,
// t_fvec2 scaling,
// t_frect uvs);
/* pushes a camera state to be used for all future unfurl_* commands */
void set_camera(const t_camera *camera);
#endif

View File

@ -1,132 +0,0 @@
/* a rendering.c mixin */
#ifndef CIRCLES_H
#define CIRCLES_H
#include "../util.h"
#include "../context.h"
#include "internal_api.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
#include <stdlib.h>
void push_circle(t_fvec2 position, float radius, t_color color) {
struct circle_primitive circle = {
.radius = radius,
.color = color,
.position = position,
};
struct primitive_2d primitive = {
.type = PRIMITIVE_2D_CIRCLE,
.circle = circle,
};
arrput(ctx.render_queue_2d, primitive);
}
/* TODO: caching and reuse scheme */
/* vertices_out and indices_out MUST BE FREED */
static void create_circle_geometry(t_fvec2 position,
t_color color,
float radius,
size_t num_vertices,
SDL_Vertex **vertices_out,
int **indices_out)
{
SDL_Vertex *vertices = cmalloc(sizeof *vertices * (num_vertices + 1));
int *indices = cmalloc(sizeof *indices * (num_vertices * 3));
/* the angle (in radians) to rotate by on each iteration */
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
vertices[0].position.x = (float)position.x;
vertices[0].position.y = (float)position.y;
vertices[0].color.r = color.r;
vertices[0].color.g = color.g;
vertices[0].color.b = color.b;
vertices[0].color.a = color.a;
vertices[0].tex_coord = (SDL_FPoint){ 0, 0 };
/* this point will rotate around the center */
float start_x = 0.0f - radius;
float start_y = 0.0f;
for (size_t i = 1; i < num_vertices + 1; ++i) {
float final_seg_rotation_angle = (float)i * seg_rotation_angle;
vertices[i].position.x =
cosf(final_seg_rotation_angle) * start_x -
sinf(final_seg_rotation_angle) * start_y;
vertices[i].position.y =
cosf(final_seg_rotation_angle) * start_y +
sinf(final_seg_rotation_angle) * start_x;
vertices[i].position.x += position.x;
vertices[i].position.y += position.y;
vertices[i].color.r = color.r;
vertices[i].color.g = color.g;
vertices[i].color.b = color.b;
vertices[i].color.a = color.a;
vertices[i].tex_coord = (SDL_FPoint){ 0, 0 };
size_t triangle_offset = 3 * (i - 1);
/* center point index */
indices[triangle_offset] = 0;
/* generated point index */
indices[triangle_offset + 1] = (int)i;
size_t index = (i + 1) % num_vertices;
if (index == 0)
index = num_vertices;
indices[triangle_offset + 2] = (int)index;
}
*vertices_out = vertices;
*indices_out = indices;
}
static void render_circle(const struct circle_primitive *circle) {
SDL_Vertex *vertices = NULL;
int *indices = NULL;
int num_vertices = (int)circle->radius;
create_circle_geometry(circle->position,
circle->color,
circle->radius,
num_vertices,
&vertices,
&indices);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2,
GL_FLOAT,
sizeof (SDL_Vertex),
&vertices->position);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4,
GL_UNSIGNED_BYTE,
sizeof (SDL_Vertex),
&vertices->color);
glDrawElements(GL_TRIANGLES,
num_vertices * 3,
GL_UNSIGNED_INT,
indices);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
free(vertices);
free(indices);
}
#endif

View File

@ -1,89 +0,0 @@
#ifndef RENDERING_INTERNAL_API_H
#define RENDERING_INTERNAL_API_H
#include "../util.h"
#include "../textures/internal_api.h"
#include <SDL2/SDL.h>
#include <glad/glad.h>
#include <stdbool.h>
struct sprite_primitive {
t_frect rect;
t_color color;
float rotation;
t_texture_key texture_key;
bool flip_x;
bool flip_y;
};
struct rect_primitive {
t_frect rect;
t_color color;
};
struct circle_primitive {
float radius;
t_color color;
t_fvec2 position;
};
enum primitive_2d_type {
PRIMITIVE_2D_SPRITE,
PRIMITIVE_2D_RECT,
PRIMITIVE_2D_CIRCLE,
};
struct primitive_2d {
enum primitive_2d_type type;
union {
struct sprite_primitive sprite;
struct rect_primitive rect;
struct circle_primitive circle;
};
};
/* union for in-place recalculation of texture coordinates */
union uncolored_space_triangle {
/* pending for sending, uvs are not final as texture atlases could update */
struct uncolored_space_triangle_primitive {
t_fvec3 v0;
t_fvec2 uv0; /* in pixels */
t_fvec3 v1;
t_fvec2 uv1; /* in pixels */
t_fvec3 v2;
t_fvec2 uv2; /* in pixels */
} primitive;
/* TODO: have it packed? */
/* structure that is passed in opengl vertex array */
struct uncolored_space_triangle_payload {
t_fvec3 v0;
t_fvec2 uv0;
t_fvec3 v1;
t_fvec2 uv1;
t_fvec3 v2;
t_fvec2 uv2;
} payload;
};
/* batch of primitives with overlapping properties */
struct mesh_batch {
GLuint buffer; /* server side storage */
uint8_t *primitives;
};
struct mesh_batch_item {
t_texture_key key;
struct mesh_batch value;
};
/* renders the background, then the primitives in all render queues */
void render(void);
/* clears all render queues */
void render_queue_clear(void);
#endif

View File

@ -1,41 +0,0 @@
/* a rendering.c mixin */
#ifndef QUAD_ELEMENT_BUFFER_H
#define QUAD_ELEMENT_BUFFER_H
#include <glad/glad.h>
#include <stddef.h>
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
static void bind_quad_element_buffer(void) {
static GLuint buffer = 0;
/* it's only generated once at runtime */
if (buffer == 0) {
glGenBuffers(1, &buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
QUAD_ELEMENT_BUFFER_LENGTH * 6,
NULL,
GL_STATIC_DRAW);
uint16_t *const indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER,
GL_WRITE_ONLY);
for (uint16_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
indices[i * 6 + 0] = (uint16_t)(i * 4 + 0);
indices[i * 6 + 1] = (uint16_t)(i * 4 + 1);
indices[i * 6 + 2] = (uint16_t)(i * 4 + 2);
indices[i * 6 + 3] = (uint16_t)(i * 4 + 2);
indices[i * 6 + 4] = (uint16_t)(i * 4 + 3);
indices[i * 6 + 5] = (uint16_t)(i * 4 + 0);
}
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
} else
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
}
#endif

View File

@ -1,330 +0,0 @@
/* a rendering.c mixin */
#ifndef SPRITES_H
#define SPRITES_H
#include "../rendering.h"
#include "../context.h"
#include "../util.h"
#include "../textures/internal_api.h"
#include "quad_element_buffer.h"
#include "internal_api.h"
#include <stb_ds.h>
#include <stdbool.h>
#include <stddef.h>
/* interleaved vertex array data */
/* TODO: use int16_t for uvs */
/* TODO: use packed types? */
/* TODO: int16_t could be used for positioning, but we would need to have more CPU calcs */
struct sprite_primitive_payload {
/* upper-left */
t_fvec2 v0;
t_fvec2 uv0;
t_color c0;
/* bottom-left */
t_fvec2 v1;
t_fvec2 uv1;
t_color c1;
/* bottom-right */
t_fvec2 v2;
t_fvec2 uv2;
t_color c2;
/* upper-right */
t_fvec2 v3;
t_fvec2 uv3;
t_color c3;
};
struct sprite_primitive_payload_without_color {
/* upper-left */
t_fvec2 v0;
t_fvec2 uv0;
/* bottom-left */
t_fvec2 v1;
t_fvec2 uv1;
/* bottom-right */
t_fvec2 v2;
t_fvec2 uv2;
/* upper-right */
t_fvec2 v3;
t_fvec2 uv3;
};
/*
* an implementation note:
* try to avoid doing expensive work in the push functions,
* because they will be called multiple times in the main loop
* before anything is really rendered
*/
/* TODO: it might make sense to infer alpha channel presence / meaningfulness for textures in atlas */
/* so that they are rendered with no blend / batched in a way to reduce overdraw automatically */
void push_sprite(const t_push_sprite_args args) {
struct sprite_primitive sprite = {
.rect = args.rect,
.color = m_or(args, color, ((t_color) { 255, 255, 255, 255 })),
.rotation = m_or(args, rotation, 0.0f),
.texture_key = textures_get_key(&ctx.texture_cache, args.path),
.flip_x = m_or(args, flip_x, false),
.flip_y = m_or(args, flip_y, false),
};
struct primitive_2d primitive = {
.type = PRIMITIVE_2D_SPRITE,
.sprite = sprite,
};
arrput(ctx.render_queue_2d, primitive);
}
static struct sprite_batch {
size_t size; /* how many primitives are in current batch */
int atlas_id;
enum texture_mode mode;
bool constant_colored; /* whether colored batch is uniformly colored */
} collect_sprite_batch(const struct primitive_2d *primitives, size_t len) {
/* assumes that first primitive is already a sprite */
struct sprite_batch batch = {
.atlas_id =
textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key),
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
.constant_colored = true,
};
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
/* batch size is clamped so that reallocated short indices could be used */
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
len = QUAD_ELEMENT_BUFFER_LENGTH;
for (size_t i = 0; i < len; ++i) {
const struct primitive_2d *const current = &primitives[i];
/* don't touch things other than sprites */
if (current->type != PRIMITIVE_2D_SPRITE)
break;
/* only collect the same blend modes */
const enum texture_mode mode = textures_get_mode(&ctx.texture_cache, current->sprite.texture_key);
if (mode != batch.mode)
break;
/* only collect the same texture atlases */
if (textures_get_atlas_id(&ctx.texture_cache, current->sprite.texture_key)
!= batch.atlas_id)
break;
/* if all are modulated the same we can skip sending the color data */
if (batch.constant_colored && *(const uint32_t *)&current->sprite.color == uniform_color)
batch.constant_colored = false;
++batch.size;
}
return batch;
}
/* assumes that orthogonal matrix setup is done already */
static void render_sprites(const struct primitive_2d primitives[],
const struct sprite_batch batch)
{
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
static GLuint vertex_array = 0;
if (vertex_array == 0)
glGenBuffers(1, &vertex_array);
if (batch.mode == TEXTURE_MODE_GHOSTLY) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_ALWAYS);
glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST);
} else if (batch.mode == TEXTURE_MODE_SEETHROUGH) {
glDisable(GL_BLEND);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_EQUAL, 1.0f);
} else {
glDisable(GL_BLEND);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glDisable(GL_ALPHA_TEST);
}
size_t payload_size;
if (!batch.constant_colored)
payload_size = sizeof (struct sprite_primitive_payload);
else
payload_size = sizeof (struct sprite_primitive_payload_without_color);
glBindBuffer(GL_ARRAY_BUFFER, vertex_array);
glBufferData(GL_ARRAY_BUFFER,
payload_size * batch.size,
NULL,
GL_STREAM_DRAW);
const t_frect dims =
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
/* vertex population over a mapped buffer */
{
/* TODO: check errors, ensure alignment ? */
void *const payload = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
for (size_t i = 0; i < batch.size; ++i) {
/* render opaques front to back */
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
const struct sprite_primitive sprite = primitives[cur].sprite;
const t_frect srcrect =
textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
const float yr = srcrect.y / dims.h;
t_fvec2 uv0 = { xr + wr * sprite.flip_x, yr + hr * sprite.flip_y };
t_fvec2 uv1 = { xr + wr * sprite.flip_x, yr + hr * !sprite.flip_y };
t_fvec2 uv2 = { xr + wr * !sprite.flip_x, yr + hr * !sprite.flip_y };
t_fvec2 uv3 = { xr + wr * !sprite.flip_x, yr + hr * sprite.flip_y };
t_fvec2 v0, v1, v2, v3;
/* todo: fast PI/2 degree divisible rotations? */
if (sprite.rotation == 0.0f) {
/* non-rotated case */
v0 = (t_fvec2){ sprite.rect.x, sprite.rect.y };
v1 = (t_fvec2){ sprite.rect.x, sprite.rect.y + sprite.rect.h };
v2 = (t_fvec2){ sprite.rect.x + sprite.rect.w, sprite.rect.y + sprite.rect.h };
v3 = (t_fvec2){ sprite.rect.x + sprite.rect.w, sprite.rect.y };
} else if (sprite.rect.w == sprite.rect.h) {
/* rotated square case */
const t_fvec2 c = frect_center(sprite.rect);
const t_fvec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
const t_fvec2 d = {
.x = t.x * sprite.rect.w * (float)M_SQRT1_2,
.y = t.y * sprite.rect.h * (float)M_SQRT1_2,
};
v0 = (t_fvec2){ c.x - d.x, c.y - d.y };
v1 = (t_fvec2){ c.x - d.y, c.y + d.x };
v2 = (t_fvec2){ c.x + d.x, c.y + d.y };
v3 = (t_fvec2){ c.x + d.y, c.y - d.x };
} else {
/* rotated non-square case*/
const t_fvec2 c = frect_center(sprite.rect);
const t_fvec2 t = fast_cossine(sprite.rotation);
const t_fvec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 };
v0 = (t_fvec2){ c.x + t.x * -h.x - t.y * -h.y, c.y + t.y * -h.x + t.x * -h.y };
v1 = (t_fvec2){ c.x + t.x * -h.x - t.y * +h.y, c.y + t.y * -h.x + t.x * +h.y };
v2 = (t_fvec2){ c.x + t.x * +h.x - t.y * +h.y, c.y + t.y * +h.x + t.x * +h.y };
v3 = (t_fvec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
}
if (!batch.constant_colored)
((struct sprite_primitive_payload *)payload)[i] = (struct sprite_primitive_payload) {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
.uv0 = uv0,
.uv1 = uv1,
.uv2 = uv2,
.uv3 = uv3,
/* equal for all (flat shaded) */
.c0 = sprite.color,
.c1 = sprite.color,
.c2 = sprite.color,
.c3 = sprite.color,
};
else
((struct sprite_primitive_payload_without_color *)payload)[i] = (struct sprite_primitive_payload_without_color) {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
.uv0 = uv0,
.uv1 = uv1,
.uv2 = uv2,
.uv3 = uv3,
};
}
glUnmapBuffer(GL_ARRAY_BUFFER);
}
GLsizei off;
GLsizei voff;
GLsizei uvoff;
if (!batch.constant_colored) {
off = offsetof(struct sprite_primitive_payload, v1);
voff = offsetof(struct sprite_primitive_payload, v0);
uvoff = offsetof(struct sprite_primitive_payload, uv0);
} else {
off = offsetof(struct sprite_primitive_payload_without_color, v1);
voff = offsetof(struct sprite_primitive_payload_without_color, v0);
uvoff = offsetof(struct sprite_primitive_payload_without_color, uv0);
}
/* vertex specification */
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2,
GL_FLOAT,
off,
(void *)(size_t)voff);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2,
GL_FLOAT,
off,
(void *)(size_t)uvoff);
if (!batch.constant_colored) {
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4,
GL_UNSIGNED_BYTE,
off,
(void *)offsetof(struct sprite_primitive_payload, c0));
} else
glColor4ub(primitives[0].sprite.color.r,
primitives[0].sprite.color.g,
primitives[0].sprite.color.b,
primitives[0].sprite.color.a);
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key, GL_TEXTURE_2D);
bind_quad_element_buffer();
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)batch.size, GL_UNSIGNED_SHORT, NULL);
/* clear the state */
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
#endif

View File

@ -1,118 +0,0 @@
/* a rendering.c mixin */
#ifndef TRIANGLES_H
#define TRIANGLES_H
#include "../context.h"
#include "internal_api.h"
#include "../textures/internal_api.h"
#include <stb_ds.h>
/* TODO: automatic handling of repeating textures */
/* for that we could allocate a loner texture */
void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2)
{
const t_texture_key texture_key = textures_get_key(&ctx.texture_cache, path);
struct mesh_batch_item *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
if (!batch_p) {
struct mesh_batch item = {0};
hmput(ctx.uncolored_mesh_batches, texture_key, item);
batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */
}
union uncolored_space_triangle triangle = { .primitive = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.uv1 = m_to_fvec2(uv1),
.uv0 = m_to_fvec2(uv0),
.uv2 = m_to_fvec2(uv2),
}};
union uncolored_space_triangle *triangles =
(union uncolored_space_triangle *)batch_p->value.primitives;
arrpush(triangles, triangle);
batch_p->value.primitives = (uint8_t *)triangles;
}
static void draw_uncolored_space_traingle_batch(struct mesh_batch *batch,
t_texture_key texture_key)
{
size_t primitives_len = arrlenu(batch->primitives);
if (primitives_len == 0)
return;
/* create vertex array object */
if (batch->buffer == 0)
glGenBuffers(1, &batch->buffer);
/* TODO: try using mapped buffers while building batches instead? */
/* this way we could skip client side copy that is kept until commitment */
/* alternatively we could commit glBufferSubData based on a threshold */
/* update pixel-based uvs to correspond with texture atlases */
for (size_t i = 0; i < primitives_len; ++i) {
struct uncolored_space_triangle_payload *payload =
&((union uncolored_space_triangle *)batch->primitives)[i].payload;
const t_frect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
const t_frect dims = textures_get_dims(&ctx.texture_cache, texture_key);
const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
const float yr = srcrect.y / dims.h;
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr;
payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr;
payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr;
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
}
textures_bind(&ctx.texture_cache, texture_key, GL_TEXTURE_2D);
glBindBuffer(GL_ARRAY_BUFFER, batch->buffer);
/* upload batched data */
glBufferData(GL_ARRAY_BUFFER,
primitives_len * sizeof (struct uncolored_space_triangle_payload),
batch->primitives,
GL_STREAM_DRAW);
/* vertex specification*/
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,
GL_FLOAT,
offsetof(struct uncolored_space_triangle_payload, v1),
(void *)offsetof(struct uncolored_space_triangle_payload, v0));
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2,
GL_FLOAT,
offsetof(struct uncolored_space_triangle_payload, v1),
(void *)offsetof(struct uncolored_space_triangle_payload, uv0));
/* commit for drawing */
glDrawArrays(GL_TRIANGLES, 0, 3 * (GLint)primitives_len);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
/* invalidate the buffer immediately */
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
#endif

View File

@ -1,139 +0,0 @@
#include "scripting.h"
#include "util.h"
#include "context.h"
#include <SDL2/SDL.h>
#include <umka_api.h>
#include <physfs.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
static void msgbox(UmkaStackSlot *params, UmkaStackSlot *result) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION,
params[1].ptrVal, params[0].ptrVal, NULL);
}
static void umka_log_info(UmkaStackSlot *params, UmkaStackSlot *result) {
log_info(params[0].ptrVal);
}
static void umka_log_critical(UmkaStackSlot *params, UmkaStackSlot *result) {
log_critical(params[0].ptrVal);
}
static void umka_log_warn(UmkaStackSlot *params, UmkaStackSlot *result) {
log_warn(params[0].ptrVal);
}
static void is_action_pressed(UmkaStackSlot *params, UmkaStackSlot *result) {
struct state *state = params[1].ptrVal;
t_ctx *ctx = state->hidden_ptr;
bool value = input_is_action_pressed(&ctx->input, params[0].ptrVal);
result->uintVal = value;
}
static void is_action_just_pressed(UmkaStackSlot *params, UmkaStackSlot *result) {
struct state *state = params[1].ptrVal;
t_ctx *ctx = state->hidden_ptr;
bool value = input_is_action_just_pressed(&ctx->input, params[0].ptrVal);
result->uintVal = value;
}
static void is_action_just_released(UmkaStackSlot *params, UmkaStackSlot *result) {
struct state *state = params[1].ptrVal;
t_ctx *ctx = state->hidden_ptr;
bool value = input_is_action_just_released(&ctx->input, params[0].ptrVal);
result->uintVal = value;
}
static void get_action_position(UmkaStackSlot *params, UmkaStackSlot *result) {
struct state *state = params[2].ptrVal;
t_ctx *ctx = state->hidden_ptr;
t_fvec2 *position = params[0].ptrVal;
*position = input_get_action_position(&ctx->input, params[1].ptrVal);
// the result is in a hidden result pointer allocated by Umka
result->ptrVal = params[0].ptrVal;
}
static void register_api(void *umka) {
umkaAddFunc(umka, "msgbox", msgbox);
umkaAddFunc(umka, "logInfo", umka_log_info);
umkaAddFunc(umka, "logCritical", umka_log_critical);
umkaAddFunc(umka, "logWarn", umka_log_warn);
umkaAddFunc(umka, "cImplIsActionPressed", is_action_pressed);
umkaAddFunc(umka, "cImplIsActionJustPressed", is_action_just_pressed);
umkaAddFunc(umka, "cImplIsActionJustReleased", is_action_just_released);
umkaAddFunc(umka, "getActionPosition", get_action_position);
}
bool scripting_init(t_ctx *ctx) {
if (!PHYSFS_exists("/scripts/main.um")) {
CRY("Failed to initialize scripting", "Could not find a main.um (we need it)");
return false;
}
ctx->umka = umkaAlloc();
char *main_script = file_to_str("/scripts/main.um");
bool umka_ok = umkaInit(ctx->umka,
"main.um",
main_script,
UMKA_STACK_SIZE,
NULL,
ctx->argc,
ctx->argv,
false,
false,
NULL);
free(main_script);
if (!umka_ok) {
CRY("Failed to initialize scripting", "Unknown Umka error");
return false;
}
/* all Umka files are compiled even if they're never used */
char **dir_file_names = PHYSFS_enumerateFiles("/scripts");
for (char **i = dir_file_names; *i != NULL; ++i) {
char *file_name = *i;
if (!strends(file_name, ".um"))
continue;
/* got this one already */
if (strcmp(file_name, "main.um") == 0)
continue;
/* need to figure out the actual path (as opposed to the lone file name) */
const char *path_prefix = "/scripts/";
size_t path_size = snprintf(NULL, 0, "%s%s", path_prefix, file_name) + 1;
char *path = cmalloc(path_size);
snprintf(path, path_size, "%s%s", path_prefix, file_name);
char *contents = file_to_str(path);
umkaAddModule(ctx->umka, file_name, contents);
free(path);
free(contents);
}
PHYSFS_freeList(dir_file_names);
register_api(ctx->umka);
if (!umkaCompile(ctx->umka)) {
cry_umka(ctx->umka);
return false;
}
return true;
}
void scripting_deinit(t_ctx *ctx) {
umkaFree(ctx->umka);
}

View File

@ -1,18 +0,0 @@
#ifndef SCRIPTING_H
#define SCRIPTING_H
#include <umka_api.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct context t_ctx;
struct state {
t_ctx *hidden_ptr;
uint64_t tick_count;
};
bool scripting_init(void);
#endif

View File

@ -1,68 +0,0 @@
#include "elf.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/auxv.h>
#include <elf.h>
#include <linux/limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
bool infer_elf_section_bounds(const char *const restrict name,
const char **restrict vm_start,
const char **restrict vm_end)
{
bool result = false;
char buf[PATH_MAX];
ssize_t l = readlink("/proc/self/exe", buf, PATH_MAX);
if (l == -1)
goto ERR_CANT_READLINK;
buf[l] = 0; /* readlink() doesn't write a terminator */
int elf = open(buf, O_RDONLY);
if (elf == -1)
goto ERR_CANT_OPEN_SELF;
/* elf header */
Elf64_Ehdr ehdr;
read(elf, &ehdr, sizeof ehdr);
if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
ehdr.e_ident[EI_MAG3] != ELFMAG3)
goto ERR_NOT_ELF;
/* section header string table */
Elf64_Shdr shstrdr;
lseek(elf, ehdr.e_shoff + ehdr.e_shstrndx * sizeof (Elf64_Shdr), SEEK_SET);
read(elf, &shstrdr, sizeof shstrdr);
char *sh = malloc(shstrdr.sh_size);
lseek(elf, shstrdr.sh_offset, SEEK_SET);
read(elf, sh, shstrdr.sh_size);
/* walk sections searching for needed name */
lseek(elf, ehdr.e_shoff, SEEK_SET);
for (size_t s = 0; s < ehdr.e_shnum; ++s) {
Elf64_Shdr shdr;
read(elf, &shdr, sizeof shdr);
if (strcmp(&sh[shdr.sh_name], name) == 0) {
result = true;
*vm_start = getauxval(AT_ENTRY) - ehdr.e_entry + (char *)shdr.sh_addr;
*vm_end = getauxval(AT_ENTRY) - ehdr.e_entry + (char *)shdr.sh_addr + shdr.sh_size;
break;
}
}
free(sh);
ERR_NOT_ELF:
close(elf);
ERR_CANT_OPEN_SELF:
ERR_CANT_READLINK:
return result;
}

View File

@ -1,10 +0,0 @@
#ifndef ELF_H
#define ELF_H
#include <stdbool.h>
bool infer_elf_section_bounds(const char *restrict name,
const char **restrict vm_start,
const char **restrict vm_end);
#endif

View File

@ -1,5 +0,0 @@
#include "text.h"
#include "SDL.h"
#include "SDL_ttf.h"

View File

@ -1,26 +0,0 @@
#ifndef TEXT_H
#define TEXT_H
#include "util.h"
struct text {
char *text;
t_color color;
int ptsize;
};
struct text_cache_item {
char *key;
struct text *value;
};
struct text_cache {
struct text_cache_item *hash;
};
#endif

View File

@ -1,86 +0,0 @@
#ifndef TEXTURES_INTERNAL_API_H
#define TEXTURES_INTERNAL_API_H
#include "../util.h"
#include "../textures/modes.h"
#include <SDL2/SDL.h>
#include <stb_rect_pack.h>
#include <glad/glad.h>
#include <stdbool.h>
struct texture {
t_frect srcrect; /* position in atlas */
SDL_Surface *data; /* original image data */
int atlas_index;
GLuint loner_texture; /* stored directly for loners, == 0 means atlas_index should be used */
enum texture_mode mode;
};
struct texture_cache_item {
char *key;
struct texture value;
};
struct texture_cache {
SDL_Window *window; /* from context */
struct texture_cache_item *hash;
stbrp_node *node_buffer; /* used internally by stb_rect_pack */
SDL_Surface **atlas_surfaces;
GLuint *atlas_textures; /* shared by atlas textures */
int atlas_index; /* atlas that is currently being built */
bool is_dirty; /* current atlas needs to be recreated */
};
/* type safe structure for persistent texture handles */
typedef struct { uint16_t id; } t_texture_key;
/* tests whether given key structure corresponds to any texture */
#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1)
void textures_cache_init(struct texture_cache *cache, SDL_Window *window);
void textures_cache_deinit(struct texture_cache *cache);
/* for debugging */
void textures_dump_atlases(struct texture_cache *cache);
/* loads an image if it isn't in the cache, otherwise a no-op. */
/* can be called from anywhere at any time after init, useful if you want to */
/* preload textures you know will definitely be used */
// void textures_load(struct texture_cache *cache, const char *path);
/* repacks the current texture atlas based on the texture cache if needed */
/* any previously returned srcrect results are invalidated after that */
/* call it every time before rendering */
void textures_update_atlas(struct texture_cache *cache);
/* returns a persistent handle to some texture in cache, loading it if needed */
/* check the result with m_texture_key_is_valid() */
t_texture_key textures_get_key(struct texture_cache *cache, const char *path);
/* returns a rect in a texture cache of the given key */
t_frect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key);
/* returns a rect of dimensions of the whole texture (whole atlas) */
t_frect textures_get_dims(const struct texture_cache *cache, t_texture_key key);
/* returns an identifier that is equal for all textures placed in the same atlas */
int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key key);
/* binds atlas texture in opengl state */
void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target);
/* returns helpful information about contents of alpha channel in given texture */
enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key);
/* returns the number of atlases in the cache */
size_t textures_get_num_atlases(const struct texture_cache *cache);
#endif

View File

@ -1,11 +0,0 @@
#ifndef TEXTURES_MODES_H
#define TEXTURES_MODES_H
/* alpha channel information */
enum texture_mode {
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
TEXTURE_MODE_SEETHROUGH, /* some pixels are alpha zero */
TEXTURE_MODE_GHOSTLY, /* arbitrary alpha values */
};
#endif

View File

@ -1,517 +0,0 @@
#include "internal_api.h"
#include "../config.h"
#include "../util.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <physfs.h>
#include <physfsrwops.h>
#include <stb_ds.h>
#include <stb_rect_pack.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
static SDL_Surface *image_to_surface(const char *path) {
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
if (handle == NULL)
goto fail;
SDL_Surface *result = IMG_Load_RW(handle, true);
if (result == NULL)
goto fail;
SDL_SetSurfaceBlendMode(result, SDL_BLENDMODE_NONE);
SDL_SetSurfaceRLE(result, true);
return result;
fail:
CRY(path, "Failed to load image. Aborting...");
die_abruptly();
}
static GLuint new_gl_texture(void) {
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
/* adds a new, blank atlas surface to the cache */
static void add_new_atlas(struct texture_cache *cache) {
SDL_PixelFormat *native_format =
SDL_AllocFormat(SDL_GetWindowPixelFormat(cache->window));
/* the window format won't have an alpha channel, so we figure this out */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
uint32_t a_mask = 0x000000FF;
#else
uint32_t a_mask = 0xFF000000;
#endif
SDL_Surface *new_atlas = SDL_CreateRGBSurface(0,
TEXTURE_ATLAS_SIZE,
TEXTURE_ATLAS_SIZE,
TEXTURE_ATLAS_BIT_DEPTH,
native_format->Rmask,
native_format->Gmask,
native_format->Bmask,
a_mask);
SDL_FreeFormat(native_format);
SDL_SetSurfaceBlendMode(new_atlas, SDL_BLENDMODE_NONE);
SDL_SetSurfaceRLE(new_atlas, true);
arrput(cache->atlas_surfaces, new_atlas);
arrput(cache->atlas_textures, new_gl_texture());
}
static void upload_texture_from_surface(GLuint texture, SDL_Surface *surface) {
Uint32 rmask, gmask, bmask, amask;
glBindTexture(GL_TEXTURE_2D, texture);
// glPixelStorei(GL_PACK_ALIGNMENT, 1);
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0xff000000;
gmask = 0x00ff0000;
bmask = 0x0000ff00;
amask = 0x000000ff;
#else
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0xff000000;
#endif
/* TODO: don't do it if format is compatible */
SDL_Surface* intermediate = SDL_CreateRGBSurface(0,
surface->w, surface->h, 32, rmask, gmask, bmask, amask);
SDL_BlitSurface(surface, NULL, intermediate, NULL);
SDL_LockSurface(intermediate);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA8,
surface->w,
surface->h,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
intermediate->pixels);
SDL_UnlockSurface(intermediate);
SDL_FreeSurface(intermediate);
glBindTexture(GL_TEXTURE_2D, 0);
}
static void recreate_current_atlas_texture(struct texture_cache *cache) {
/* TODO: figure out if SDL_UpdateTexture alone is faster than blitting */
/* TODO: should surfaces be freed after they cannot be referenced in atlas builing? */
SDL_Surface *atlas_surface = cache->atlas_surfaces[cache->atlas_index];
/* clear */
SDL_FillRect(atlas_surface, NULL, 0);
/* blit the texture surfaces onto the atlas */
for (size_t i = 0; i < shlenu(cache->hash); ++i) {
/* skip all that aren't part of currently built one */
if (cache->hash[i].value.atlas_index != cache->atlas_index)
continue;
/* skip loners */
if (cache->hash[i].value.loner_texture != 0)
continue;
SDL_BlitSurface(cache->hash[i].value.data,
NULL,
atlas_surface,
&(SDL_Rect){
.x = (int)cache->hash[i].value.srcrect.x,
.y = (int)cache->hash[i].value.srcrect.y,
.w = (int)cache->hash[i].value.srcrect.w,
.h = (int)cache->hash[i].value.srcrect.h,
});
}
/* texturize it! */
upload_texture_from_surface(cache->atlas_textures[cache->atlas_index], atlas_surface);
}
/* uses the textures currently in the cache to create an array of stbrp_rects */
static stbrp_rect *create_rects_from_cache(struct texture_cache *cache) {
stbrp_rect *rects = NULL;
for (size_t i = 0; i < shlenu(cache->hash); ++i) {
if (cache->hash[i].value.loner_texture != 0)
continue;
const SDL_Surface *surface_data = cache->hash[i].value.data;
stbrp_rect new_rect = {
.w = surface_data->w,
.h = surface_data->h,
};
arrput(rects, new_rect);
}
return rects;
}
/* returns an array which contains a _copy_ of every unpacked rect in rects. */
/* each of these copies will have their original index in rects saved in */
/* their `id` field, which is an int. */
static stbrp_rect *filter_unpacked_rects(stbrp_rect *rects) {
stbrp_rect *unpacked_rects = NULL;
for (size_t i = 0; i < arrlenu(rects); ++i) {
/* already packed */
if (rects[i].was_packed)
continue;
arrput(unpacked_rects, rects[i]);
/* stb_rect_pack mercifully gives you a free userdata int */
/* the index is saved there so the original array can be updated later */
unpacked_rects[arrlenu(unpacked_rects)-1].id = (int)i;
}
return unpacked_rects;
}
/* updates the original rects array with the data from packed_rects */
/* returns true if all rects were packed successfully */
static bool update_rects(struct texture_cache *cache, stbrp_rect *rects, stbrp_rect *packed_rects) {
/* !!! do not grow either of the arrays !!! */
/* the reallocation will try to reassign the array pointer, to no effect. */
/* see stb_ds.h */
bool packed_all = true;
for (size_t i = 0; i < arrlenu(packed_rects); ++i) {
/* we can check if any rects failed to be packed right here */
/* it's not ideal, but it avoids another iteration */
if (!packed_rects[i].was_packed) {
packed_all = false;
continue;
}
rects[packed_rects[i].id] = packed_rects[i];
/* while the order of the elements in the hash map is unknown to us, */
/* their equivalents in `rects` are in that same (unknown) order, which means */
/* we can use the index we had saved to find the original texture struct */
cache->hash[packed_rects[i].id].value.atlas_index = cache->atlas_index;
}
return packed_all;
}
/* updates the atlas location of every rect in the cache */
static void update_texture_rects_in_atlas(struct texture_cache *cache, stbrp_rect *rects) {
for (size_t i = 0; i < arrlenu(rects); ++i) {
cache->hash[i].value.srcrect = (t_frect) {
.x = (float)rects[i].x,
.y = (float)rects[i].y,
.w = (float)rects[i].w,
.h = (float)rects[i].h,
};
}
}
void textures_cache_init(struct texture_cache *cache, SDL_Window *window) {
cache->window = window;
sh_new_arena(cache->hash);
cache->node_buffer = cmalloc(sizeof *cache->node_buffer * TEXTURE_ATLAS_SIZE);
add_new_atlas(cache);
recreate_current_atlas_texture(cache);
}
void textures_cache_deinit(struct texture_cache *cache) {
/* free atlas textures */
for (size_t i = 0; i < arrlenu(cache->atlas_textures); ++i) {
glDeleteTextures(1, &cache->atlas_textures[i]);
}
arrfree(cache->atlas_textures);
/* free atlas surfaces */
for (size_t i = 0; i < arrlenu(cache->atlas_surfaces); ++i) {
SDL_FreeSurface(cache->atlas_surfaces[i]);
}
arrfree(cache->atlas_surfaces);
/* free cache hashes */
for (size_t i = 0; i < shlenu(cache->hash); ++i) {
SDL_FreeSurface(cache->hash[i].value.data);
}
shfree(cache->hash);
free(cache->node_buffer);
}
void textures_dump_atlases(struct texture_cache *cache) {
PHYSFS_mkdir("/dump");
const char string_template[] = "/dump/atlas%zd.png";
char buf[2048]; /* larger than will ever be necessary */
size_t i = 0;
for (; i < arrlenu(cache->atlas_surfaces); ++i) {
snprintf(buf, sizeof buf, string_template, i);
SDL_RWops *handle = PHYSFSRWOPS_openWrite(buf);
if (handle == NULL) {
CRY("Texture atlas dump failed.", "File could not be opened");
return;
}
IMG_SavePNG_RW(cache->atlas_surfaces[i], handle, true);
log_info("Dumped atlas %s", buf);
}
}
static enum texture_mode infer_texture_mode(SDL_Surface *surface) {
const uint32_t amask = surface->format->Amask;
if (amask == 0)
return TEXTURE_MODE_OPAQUE;
enum texture_mode result = TEXTURE_MODE_OPAQUE;
SDL_LockSurface(surface);
for (int i = 0; i < surface->w * surface->h; ++i) {
/* TODO: don't assume 32 bit depth ? */
t_color color;
SDL_GetRGBA(((uint32_t *)surface->pixels)[i], surface->format, &color.r, &color.g, &color.b, &color.a);
if (color.a == 0)
result = TEXTURE_MODE_SEETHROUGH;
else if (color.a != 255) {
result = TEXTURE_MODE_GHOSTLY;
break;
}
}
SDL_UnlockSurface(surface);
return result;
}
static t_texture_key textures_load(struct texture_cache *cache, const char *path) {
/* no need to do anything if it was loaded already */
const ptrdiff_t i = shgeti(cache->hash, path);
if (i >= 0)
return (t_texture_key){ (uint16_t)i };
SDL_Surface *surface = image_to_surface(path);
struct texture new_texture = {
.data = surface,
.mode = infer_texture_mode(surface),
};
/* it's a "loner texture," it doesn't fit in an atlas so it's not in one */
if (surface->w >= TEXTURE_ATLAS_SIZE || surface->h >= TEXTURE_ATLAS_SIZE) {
new_texture.loner_texture = new_gl_texture();
upload_texture_from_surface(new_texture.loner_texture, surface);
new_texture.srcrect = (t_frect) { .w = (float)surface->w, .h = (float)surface->h };
shput(cache->hash, path, new_texture);
return (t_texture_key){ (uint16_t)shgeti(cache->hash, path) };
} else {
new_texture.atlas_index = cache->atlas_index;
shput(cache->hash, path, new_texture);
cache->is_dirty = true;
return (t_texture_key){ (uint16_t)shgeti(cache->hash, path) };
}
}
void textures_update_atlas(struct texture_cache *cache) {
if (!cache->is_dirty)
return;
/* this function makes a lot more sense if you read stb_rect_pack.h */
stbrp_context pack_ctx; /* target info */
stbrp_init_target(&pack_ctx,
TEXTURE_ATLAS_SIZE,
TEXTURE_ATLAS_SIZE,
cache->node_buffer,
TEXTURE_ATLAS_SIZE);
stbrp_rect *rects = create_rects_from_cache(cache);
/* we have to keep packing, and creating atlases if necessary, */
/* until all rects have been packed. */
/* ideally, this will not iterate more than once. */
bool textures_remaining = true;
while (textures_remaining) {
stbrp_rect *rects_to_pack = filter_unpacked_rects(rects);
stbrp_pack_rects(&pack_ctx, rects_to_pack, (int)arrlen(rects_to_pack));
textures_remaining = !update_rects(cache, rects, rects_to_pack);
arrfree(rects_to_pack); /* got what we needed */
/* some textures couldn't be packed */
if (textures_remaining) {
update_texture_rects_in_atlas(cache, rects);
recreate_current_atlas_texture(cache);
/* need a new atlas for next time */
add_new_atlas(cache);
++cache->atlas_index;
}
};
update_texture_rects_in_atlas(cache, rects);
recreate_current_atlas_texture(cache);
cache->is_dirty = false;
arrfree(rects);
}
/* EXPERIMANTAL: LIKELY TO BE REMOVED! */
#ifdef __linux__ /* use rodata elf section for fast lookups of repeating textures */
#include "../system/linux/elf.h"
static const char *rodata_start;
static const char *rodata_stop;
t_texture_key textures_get_key(struct texture_cache *cache, const char *path) {
static const char *last_path = NULL;
static t_texture_key last_texture;
static struct ptr_to_texture {
const void *key;
t_texture_key value;
} *ptr_to_texture;
if (rodata_stop == NULL)
if (!infer_elf_section_bounds(".rodata", &rodata_start, &rodata_stop))
CRY("Section inference", ".rodata section lookup failed");
/* the fastest path */
if (path == last_path)
return last_texture;
else {
/* moderately fast path, by pointer hashing */
const ptrdiff_t texture = hmgeti(ptr_to_texture, path);
if (texture != -1) {
if (path >= rodata_start && path < rodata_stop)
last_path = path;
last_texture = ptr_to_texture[texture].value;
return last_texture;
}
}
/* try loading */
last_texture = textures_load(cache, path);
hmput(ptr_to_texture, path, last_texture);
if (path >= rodata_start && path < rodata_stop)
last_path = path;
return last_texture;
}
#else
t_texture_key textures_get_key(struct texture_cache *cache, const char *path) {
/* hash tables are assumed to be stable, so we just return indices */
const ptrdiff_t texture = shgeti(cache->hash, path);
/* load it if it isn't */
if (texture == -1) {
return textures_load(cache, path);
} else
return (t_texture_key){ (uint16_t)texture };
}
#endif /* generic implementation of textures_get_key() */
int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key key) {
if (m_texture_key_is_valid(key)) {
if (cache->hash[key.id].value.loner_texture != 0)
return -cache->hash[key.id].value.loner_texture;
else
return cache->hash[key.id].value.atlas_index;
} else {
CRY("Texture lookup failed.",
"Tried to get atlas id that isn't loaded.");
return 0;
}
}
t_frect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key) {
if (m_texture_key_is_valid(key)) {
return cache->hash[key.id].value.srcrect;
} else {
CRY("Texture lookup failed.",
"Tried to get texture that isn't loaded.");
return (t_frect){ 0, 0, 0, 0 };
}
}
t_frect textures_get_dims(const struct texture_cache *cache, t_texture_key key) {
if (m_texture_key_is_valid(key)) {
if (cache->hash[key.id].value.loner_texture != 0)
return cache->hash[key.id].value.srcrect;
else
return (t_frect){ .w = TEXTURE_ATLAS_SIZE, .h = TEXTURE_ATLAS_SIZE };
} else {
CRY("Texture lookup failed.",
"Tried to get texture that isn't loaded.");
return (t_frect){ 0, 0, 0, 0 };
}
}
void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target) {
if (m_texture_key_is_valid(key)) {
if (cache->hash[key.id].value.loner_texture == 0)
glBindTexture(target, cache->atlas_textures[cache->hash[key.id].value.atlas_index]);
else
glBindTexture(target, cache->hash[key.id].value.loner_texture);
} else if (key.id == 0) {
CRY("Texture binding failed.",
"Tried to get texture that isn't loaded.");
}
}
enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key) {
if (m_texture_key_is_valid(key)) {
return cache->hash[key.id].value.mode;
} else {
CRY("Texture binding failed.",
"Tried to get texture that isn't loaded.");
return TEXTURE_MODE_GHOSTLY;
}
}
size_t textures_get_num_atlases(const struct texture_cache *cache) {
return cache->atlas_index + 1;
}

View File

@ -1,231 +0,0 @@
#include "util.h"
#include "context.h"
#include <SDL2/SDL.h>
#include <physfsrwops.h>
#define STB_DS_IMPLEMENTATION
#define STBDS_ASSERT SDL_assert
#define STBDS_REALLOC(c,p,s) crealloc(p, s)
#define STBDS_FREE(c,p) free(p)
#include <stb_ds.h>
#define STB_RECT_PACK_IMPLEMENTATION
#define STBRP_ASSERT SDL_assert
#include <stb_rect_pack.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdnoreturn.h>
#include <string.h>
void cry_impl(const char *file, const int line, const char *title, const char *text) {
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
"TEARS AT %s:%d: %s ... %s", file, line, title, text);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, text, NULL);
}
static void log_impl(const char *restrict format, va_list args, SDL_LogPriority priority) {
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION,
priority,
format,
args);
}
void log_info(const char *restrict format, ...) {
va_list args;
va_start(args, format);
log_impl(format, args, SDL_LOG_PRIORITY_INFO);
va_end(args);
}
void log_critical(const char *restrict format, ...) {
va_list args;
va_start(args, format);
log_impl(format, args, SDL_LOG_PRIORITY_CRITICAL);
va_end(args);
}
void log_warn(const char *restrict format, ...) {
va_list args;
va_start(args, format);
log_impl(format, args, SDL_LOG_PRIORITY_WARN);
va_end(args);
}
noreturn static void alloc_failure_death(void) {
log_critical("Allocation failure. Aborting NOW.");
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
"MEMORY ALLOCATION FAILURE.",
"FAILED TO ALLOCATE MEMORY. "
"YOU MIGHT BE UNLUCKY. "
"THE GAME WILL EXIT NOW.",
NULL);
die_abruptly();
}
noreturn void die_abruptly(void) {
/* a zombie window will linger if we don't at least try to quit SDL */
SDL_Quit();
abort();
}
void *cmalloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL)
alloc_failure_death();
return ptr;
}
void *crealloc(void *ptr, size_t size) {
void *out = realloc(ptr, size);
if (out == NULL)
alloc_failure_death();
return out;
}
void *ccalloc(size_t num, size_t size) {
void *ptr = calloc(num, size);
if (ptr == NULL)
alloc_failure_death();
return ptr;
}
double clamp(double d, double min, double max) {
const double t = d < min ? min : d;
return t > max ? max : t;
}
float clampf(float f, float min, float max) {
const float t = f < min ? min : f;
return t > max ? max : t;
}
int clampi(int i, int min, int max) {
const int t = i < min ? min : i;
return t > max ? max : t;
}
int64_t file_to_bytes(const char *path, unsigned char **buf_out) {
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
if (handle == NULL) {
return -1;
}
int64_t data_size = SDL_RWseek(handle, 0, RW_SEEK_END);
SDL_RWseek(handle, 0, RW_SEEK_SET); /* reset offset into data */
*buf_out = cmalloc(data_size);
SDL_RWread(handle, *buf_out, sizeof **buf_out, data_size / sizeof **buf_out);
SDL_RWclose(handle); /* we got all we needed from the stream */
return data_size;
}
char *file_to_str(const char *path) {
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
if (handle == NULL) {
return NULL;
}
int64_t data_size = SDL_RWseek(handle, 0, RW_SEEK_END);
SDL_RWseek(handle, 0, RW_SEEK_SET); /* reset offset into data */
char *str_out = cmalloc(data_size + 1); /* data plus final null */
size_t len = data_size / sizeof *str_out;
SDL_RWread(handle, str_out, sizeof *str_out, len);
SDL_RWclose(handle); /* we got all we needed from the stream */
str_out[len] = '\0';
return str_out;
}
bool strends(const char *str, const char *suffix) {
size_t str_length = strlen(str);
size_t suffix_length = strlen(suffix);
if (suffix_length > str_length)
return false;
return memcmp((str + str_length) - suffix_length, suffix, suffix_length) == 0;
}
bool intersect_rect(const t_rect *a, const t_rect *b, t_rect *result) {
SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
SDL_Rect result_sdl = { 0 };
bool intersection = SDL_IntersectRect(&a_sdl, &b_sdl, &result_sdl);
*result = (t_rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
return intersection;
}
bool intersect_frect(const t_frect *a, const t_frect *b, t_frect *result) {
SDL_FRect a_sdl = { a->x, a->y, a->w, a->h };
SDL_FRect b_sdl = { b->x, b->y, b->w, b->h };
SDL_FRect result_sdl = { 0 };
bool intersection = SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
*result = (t_frect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
return intersection;
}
t_frect to_frect(t_rect rect) {
return (t_frect) {
.h = (float)rect.h,
.w = (float)rect.w,
.x = (float)rect.x,
.y = (float)rect.y,
};
}
t_fvec2 frect_center(t_frect rect) {
return (t_fvec2){
.x = rect.x + rect.w / 2,
.y = rect.y + rect.h / 2,
};
}
void tick_timer(int *value) {
*value = MAX(*value - 1, 0);
}
void tick_ftimer(float *value) {
*value = MAX(*value - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
}
bool repeat_ftimer(float *value, float at) {
*value -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
if (*value < 0.0f) {
*value += at;
return true;
}
return false;
}

View File

@ -1,167 +0,0 @@
#ifndef UTIL_H
#define UTIL_H
#include "vec.h"
#include <SDL2/SDL.h>
#include <physfs.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#include <stdnoreturn.h>
/* */
/* GENERAL UTILITIES */
/* */
void cry_impl(const char *file, const int line, const char *title, const char *text);
#define CRY(title, text) cry_impl(__FILE__, __LINE__, title, text)
#define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError())
#define CRY_PHYSFS(title) \
cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))
void log_info(const char *restrict format, ...);
void log_critical(const char *restrict format, ...);
void log_warn(const char *restrict format, ...);
/* for when there's absolutely no way to continue */
noreturn void die_abruptly(void);
/* "critical" allocation functions which will log and abort() on failure. */
/* if it is reasonable to handle this gracefully, use the standard versions. */
/* the stb implementations will be configured to use these */
void *cmalloc(size_t size);
void *crealloc(void *ptr, size_t size);
void *ccalloc(size_t num, size_t size);
/* DON'T FORGET ABOUT DOUBLE EVALUATION */
/* YOU THINK YOU WON'T, AND THEN YOU DO */
/* C23's typeof could fix it, so i will */
/* leave them as macros to be replaced. */
/* use the macro in tgmath.h for floats */
#define MAX SDL_max
#define MIN SDL_min
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288 /**< pi */
#endif
/* multiply by these to convert degrees <---> radians */
#define DEG2RAD (M_PI / 180)
#define RAD2DEG (180 / M_PI)
/* TODO: this is why generics were invented. sorry, i'm tired today */
double clamp(double d, double min, double max);
float clampf(float f, float min, float max);
int clampi(int i, int min, int max);
/* sets buf_out to a pointer to a byte buffer which must be freed. */
/* returns the size of this buffer. */
int64_t file_to_bytes(const char *path, unsigned char **buf_out);
/* returns a pointer to a string which must be freed */
char *file_to_str(const char *path);
/* returns true if str ends with suffix */
bool strends(const char *str, const char *suffix);
/* */
/* GAME LOGIC UTILITIES */
/* */
/* 32-bit color data */
typedef struct color {
_Alignas(4)
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} t_color;
/* a rectangle with the origin at the upper left (integer) */
typedef struct rect {
_Alignas(16)
int32_t x;
int32_t y;
int32_t w;
int32_t h;
} t_rect;
/* a rectangle with the origin at the upper left (floating point) */
typedef struct frect {
_Alignas(16)
float x;
float y;
float w;
float h;
} t_frect;
bool intersect_rect(const t_rect *a, const t_rect *b, t_rect *result);
bool intersect_frect(const t_frect *a, const t_frect *b, t_frect *result);
/* TODO: generics and specials (see m_to_fvec2() for an example)*/
t_frect to_frect(t_rect rect);
t_fvec2 frect_center(t_frect rect);
typedef struct matrix4 {
t_fvec4 row[4];
} t_matrix4;
/* decrements an lvalue (which should be an int), stopping at 0 */
/* meant for tick-based timers in game logic */
/*
* example:
* tick_timer(&player->jump_air_timer);
*/
void tick_timer(int *value);
/* decrements a floating point second-based timer, stopping at 0.0 */
/* meant for poll based real time logic in game logic */
/* note that it should be decremented only on the next tick after its creation */
void tick_ftimer(float *value);
/* same as `tick_ftimer` but instead of clamping it repeats */
/* returns true if value was cycled */
bool repeat_ftimer(float *value, float at);
/* http://www.azillionmonkeys.com/qed/sqroot.html */
static inline float fast_sqrt(float x)
{
union {
float f;
uint32_t u;
} pun = {.f = x};
pun.u += 127 << 23;
pun.u >>= 1;
return pun.f;
}
static inline t_fvec2 fast_cossine(float a) {
const float s = sinf(a);
return (t_fvec2){
.x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1),
.y = s
};
}
#endif

156
src/vec.h
View File

@ -1,156 +0,0 @@
#ifndef VEC_H
#define VEC_H
#include <stdint.h>
#include <math.h>
/* a point in some space (integer) */
typedef struct vec2 {
_Alignas(8)
int32_t x;
int32_t y;
} t_vec2;
/* a point in some space (floating point) */
typedef struct fvec2 {
_Alignas(8)
float x;
float y;
} t_fvec2;
/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct fvec3 {
_Alignas(16)
float x;
float y;
float z;
} t_fvec3;
/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct fvec4 {
_Alignas(16)
float x;
float y;
float z;
float w;
} t_fvec4;
/* a point in some space (short) */
typedef struct shvec2 {
_Alignas(4)
int16_t x;
int16_t y;
} t_shvec2;
/* aren't macros to prevent double evaluation with side effects */
/* maybe could be inlined? i hope LTO will resolve this */
static inline t_fvec2 fvec2_from_vec2(t_vec2 vec) {
return (t_fvec2) {
.x = (float)vec.x,
.y = (float)vec.y,
};
}
static inline t_fvec2 fvec2_from_shvec2(t_shvec2 vec) {
return (t_fvec2) {
.x = (float)vec.x,
.y = (float)vec.y,
};
}
static inline t_fvec3 fvec3_add(t_fvec3 a, t_fvec3 b) {
return (t_fvec3) { a.x + b.x, a.y + b.y, a.z + b.z };
}
static inline t_fvec3 fvec3_sub(t_fvec3 a, t_fvec3 b) {
return (t_fvec3) { a.x - b.x, a.y - b.y, a.z - b.z };
}
static inline t_fvec2 fvec2_scale(t_fvec2 a, float s) {
return (t_fvec2) { a.x * s, a.y * s };
}
static inline t_fvec3 fvec3_scale(t_fvec3 a, float s) {
return (t_fvec3) { a.x * s, a.y * s, a.z * s };
}
static inline float fvec3_dot(t_fvec3 a, t_fvec3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static inline t_fvec3 fvec3_cross(t_fvec3 a, t_fvec3 b) {
return (t_fvec3) {
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x,
};
}
/* TODO: fast_sqrt version? */
static inline t_fvec3 fvec3_norm(t_fvec3 a) {
const float n = sqrtf(fvec3_dot(a, a));
/* TODO: do we need truncating over epsilon as cglm does? */
return fvec3_scale(a, 1.0f / n);
}
static inline t_fvec3 fvec3_rotate(t_fvec3 v, float angle, t_fvec3 axis) {
/* from cglm */
t_fvec3 v1, v2, k;
float c, s;
c = cosf(angle);
s = sinf(angle);
k = fvec3_norm(axis);
/* Right Hand, Rodrigues' rotation formula:
v = v*cos(t) + (kxv)sin(t) + k*(k.v)(1 - cos(t))
*/
v1 = fvec3_scale(v, c);
v2 = fvec3_cross(k, v);
v2 = fvec3_scale(v2, s);
v1 = fvec3_add(v1, v2);
v2 = fvec3_scale(k, fvec3_dot(k, v) * (1.0f - c));
v = fvec3_add(v1, v2);
return v;
}
#define m_to_fvec2(p_any_vec2) (_Generic((p_any_vec2), \
t_vec2: fvec2_from_vec2, \
t_shvec2: fvec2_from_shvec2 \
)(p_any_vec2))
#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
t_fvec3: fvec3_sub \
)(p_any_vec0, p_any_vec1))
#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \
t_fvec2: fvec2_scale, \
t_fvec3: fvec3_scale \
)(p_any_vec, p_any_scalar))
#define m_vec_dot(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
t_fvec3: fvec3_dot \
)(p_any_vec0, p_any_vec1))
#define m_vec_cross(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
t_fvec3: fvec3_cross \
)(p_any_vec0, p_any_vec1))
#define m_vec_norm(p_any_vec) (_Generic((p_any_vec), \
t_fvec3: fvec3_norm \
)(p_any_vec))
#endif