remove symlink as windows is shit, src to townengine
This commit is contained in:
346
src/audio.c
346
src/audio.c
@ -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);
|
||||
}
|
||||
}
|
36
src/audio.h
36
src/audio.h
@ -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
|
@ -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
|
52
src/camera.c
52
src/camera.c
@ -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;
|
||||
}
|
21
src/camera.h
21
src/camera.h
@ -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
|
35
src/config.h
35
src/config.h
@ -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
|
@ -1,3 +0,0 @@
|
||||
#include "context.h"
|
||||
|
||||
t_ctx ctx = {0};
|
@ -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
|
@ -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
|
313
src/input.c
313
src/input.c
@ -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();
|
||||
}
|
100
src/input.h
100
src/input.h
@ -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
|
@ -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
|
@ -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
|
@ -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
|
388
src/main.c
388
src/main.c
@ -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, ¤t_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;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
#ifndef PROCGEN_H
|
||||
#define PROCGEN_H
|
||||
|
||||
#include "procgen/perlin.h"
|
||||
|
||||
#endif
|
@ -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
|
@ -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;
|
||||
}
|
212
src/rendering.c
212
src/rendering.c
@ -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(¤t->rect);
|
||||
break;
|
||||
case PRIMITIVE_2D_CIRCLE:
|
||||
render_circle(¤t->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);
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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 *)¤t->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
|
@ -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
|
139
src/scripting.c
139
src/scripting.c
@ -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);
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
@ -1,5 +0,0 @@
|
||||
#include "text.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_ttf.h"
|
||||
|
26
src/text.h
26
src/text.h
@ -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
|
@ -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
|
@ -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
|
@ -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;
|
||||
}
|
231
src/util.c
231
src/util.c
@ -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;
|
||||
}
|
167
src/util.h
167
src/util.h
@ -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
156
src/vec.h
@ -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
|
Reference in New Issue
Block a user