opengl moment #1
@ -58,7 +58,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_FILES})
|
|||||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
C_STANDARD 11
|
C_STANDARD 11
|
||||||
C_STANDARD_REQUIRED ON
|
C_STANDARD_REQUIRED ON
|
||||||
C_EXTENSIONS OFF)
|
C_EXTENSIONS ON) # extensions are required by stb_ds.h
|
||||||
|
|
||||||
# distribution definitions
|
# distribution definitions
|
||||||
set(ORGANIZATION_NAME "wanp" CACHE STRING
|
set(ORGANIZATION_NAME "wanp" CACHE STRING
|
||||||
|
@ -23,9 +23,7 @@ typedef struct context {
|
|||||||
struct sprite_primitive *render_queue_sprites;
|
struct sprite_primitive *render_queue_sprites;
|
||||||
struct rect_primitive *render_queue_rectangles;
|
struct rect_primitive *render_queue_rectangles;
|
||||||
struct circle_primitive *render_queue_circles;
|
struct circle_primitive *render_queue_circles;
|
||||||
|
struct mesh_batch_item *uncolored_mesh_batches;
|
||||||
struct mesh_batch *uncolored_mesh_batches; /* texture_cache reflected */
|
|
||||||
struct mesh_batch_item *uncolored_mesh_batches_loners; /* path reflected */
|
|
||||||
|
|
||||||
struct audio_channel_item *audio_channels;
|
struct audio_channel_item *audio_channels;
|
||||||
SDL_AudioDeviceID audio_device;
|
SDL_AudioDeviceID audio_device;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define PRIVATE_RENDERING_H
|
#define PRIVATE_RENDERING_H
|
||||||
|
|
||||||
#include "../rendering.h"
|
#include "../rendering.h"
|
||||||
|
#include "../textures.h"
|
||||||
#include "../util.h"
|
#include "../util.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
@ -13,9 +14,8 @@ struct sprite_primitive {
|
|||||||
t_frect rect;
|
t_frect rect;
|
||||||
t_color color;
|
t_color color;
|
||||||
double rotation;
|
double rotation;
|
||||||
char *path;
|
|
||||||
SDL_BlendMode blend_mode;
|
SDL_BlendMode blend_mode;
|
||||||
int atlas_index;
|
t_texture_key texture_key;
|
||||||
int layer;
|
int layer;
|
||||||
bool flip_x;
|
bool flip_x;
|
||||||
bool flip_y;
|
bool flip_y;
|
||||||
@ -32,26 +32,39 @@ struct circle_primitive {
|
|||||||
t_fvec2 position;
|
t_fvec2 position;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* batch of primitives with overlapping properties */
|
/* union for in-place recalculation of texture coordinates */
|
||||||
struct mesh_batch {
|
union uncolored_space_triangle {
|
||||||
GLuint buffer; /* server side storage */
|
/* pending for sending, uvs are not final as texture atlases could update */
|
||||||
uint8_t *data; /* client side storage */
|
struct uncolored_space_triangle_primitive {
|
||||||
// size_t buffer_len; /* element count */
|
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;
|
||||||
|
|
||||||
struct mesh_batch_item {
|
/* structure that is passed in opengl vertex array */
|
||||||
char *key;
|
struct uncolored_space_triangle_payload {
|
||||||
struct mesh_batch value;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* is structure that is in opengl vertex array */
|
|
||||||
struct uncolored_space_triangle {
|
|
||||||
t_fvec3 v0;
|
t_fvec3 v0;
|
||||||
t_fvec2 uv0;
|
t_fvec2 uv0;
|
||||||
t_fvec3 v1;
|
t_fvec3 v1;
|
||||||
t_fvec2 uv1;
|
t_fvec2 uv1;
|
||||||
t_fvec3 v2;
|
t_fvec3 v2;
|
||||||
t_fvec2 uv2;
|
t_fvec2 uv2;
|
||||||
|
} payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* batch of primitives with overlapping properties */
|
||||||
|
struct mesh_batch {
|
||||||
|
GLuint buffer; /* server side storage */
|
||||||
|
size_t buffer_len; /* element count */
|
||||||
|
uint8_t *primitives;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mesh_batch_item {
|
||||||
|
t_texture_key key;
|
||||||
|
struct mesh_batch value;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#ifndef PRIVATE_TEXTURES_H
|
#ifndef PRIVATE_TEXTURES_H
|
||||||
#define PRIVATE_TEXTURES_H
|
#define PRIVATE_TEXTURES_H
|
||||||
|
|
||||||
|
#include "../util.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <stb_rect_pack.h>
|
#include <stb_rect_pack.h>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
@ -8,10 +10,10 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct texture {
|
struct texture {
|
||||||
SDL_Rect srcrect; /* position in atlas */
|
t_rect srcrect; /* position in atlas */
|
||||||
SDL_Surface *data; /* original image data */
|
SDL_Surface *data; /* original image data */
|
||||||
GLuint loner_data; /* loner textures store their data directly */
|
int atlas_index;
|
||||||
int atlas_index; /* which atlas the texture is in */
|
GLuint loner_texture; /* stored directly for loners, == 0 means atlas_index should be used*/
|
||||||
int8_t layer;
|
int8_t layer;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,12 +29,11 @@ struct texture_cache {
|
|||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
|
|
||||||
struct texture_cache_item *hash;
|
struct texture_cache_item *hash;
|
||||||
struct texture_cache_item *loner_hash;
|
|
||||||
|
|
||||||
stbrp_node *node_buffer; /* used internally by stb_rect_pack */
|
stbrp_node *node_buffer; /* used internally by stb_rect_pack */
|
||||||
|
|
||||||
SDL_Surface **atlas_surfaces;
|
SDL_Surface **atlas_surfaces;
|
||||||
GLuint *atlas_textures;
|
GLuint *atlas_textures; /* shared by atlas textures */
|
||||||
int atlas_index;
|
int atlas_index;
|
||||||
|
|
||||||
bool is_dirty; /* current atlas needs to be recreated */
|
bool is_dirty; /* current atlas needs to be recreated */
|
||||||
|
206
src/rendering.c
206
src/rendering.c
@ -20,11 +20,8 @@ void render_queue_clear(void) {
|
|||||||
arrsetlen(ctx.render_queue_rectangles, 0);
|
arrsetlen(ctx.render_queue_rectangles, 0);
|
||||||
arrsetlen(ctx.render_queue_circles, 0);
|
arrsetlen(ctx.render_queue_circles, 0);
|
||||||
|
|
||||||
for (size_t i = 0; i < arrlenu(ctx.uncolored_mesh_batches); ++i)
|
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
|
||||||
arrsetlen(ctx.uncolored_mesh_batches[i].data, 0);
|
arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
|
||||||
|
|
||||||
for (size_t i = 0; i < shlenu(ctx.uncolored_mesh_batches_loners); ++i)
|
|
||||||
arrsetlen(ctx.uncolored_mesh_batches_loners[i].value.data, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -37,16 +34,12 @@ void render_queue_clear(void) {
|
|||||||
|
|
||||||
/* sprite */
|
/* sprite */
|
||||||
void push_sprite(char *path, t_frect rect) {
|
void push_sprite(char *path, t_frect rect) {
|
||||||
textures_load(&ctx.texture_cache, path);
|
|
||||||
|
|
||||||
struct sprite_primitive sprite = {
|
struct sprite_primitive sprite = {
|
||||||
.rect = rect,
|
.rect = rect,
|
||||||
.color = (t_color) { 255, 255, 255, 255 },
|
.color = (t_color) { 255, 255, 255, 255 },
|
||||||
.path = path,
|
|
||||||
.rotation = 0.0,
|
.rotation = 0.0,
|
||||||
.blend_mode = SDL_BLENDMODE_BLEND,
|
.blend_mode = SDL_BLENDMODE_BLEND,
|
||||||
.atlas_index =
|
.texture_key = textures_get_key(&ctx.texture_cache, path),
|
||||||
textures_get_atlas_index(&ctx.texture_cache, path),
|
|
||||||
.layer = 0,
|
.layer = 0,
|
||||||
.flip_x = false,
|
.flip_x = false,
|
||||||
.flip_y = false,
|
.flip_y = false,
|
||||||
@ -57,16 +50,12 @@ void push_sprite(char *path, t_frect rect) {
|
|||||||
|
|
||||||
|
|
||||||
void push_sprite_ex(t_frect rect, t_push_sprite_args args) {
|
void push_sprite_ex(t_frect rect, t_push_sprite_args args) {
|
||||||
textures_load(&ctx.texture_cache, args.path);
|
|
||||||
|
|
||||||
struct sprite_primitive sprite = {
|
struct sprite_primitive sprite = {
|
||||||
.rect = rect,
|
.rect = rect,
|
||||||
.color = args.color,
|
.color = args.color,
|
||||||
.path = args.path,
|
|
||||||
.rotation = args.rotation,
|
.rotation = args.rotation,
|
||||||
.blend_mode = args.blend_mode,
|
.blend_mode = args.blend_mode,
|
||||||
.atlas_index =
|
.texture_key = textures_get_key(&ctx.texture_cache, args.path),
|
||||||
textures_get_atlas_index(&ctx.texture_cache, args.path),
|
|
||||||
.layer = args.layer,
|
.layer = args.layer,
|
||||||
.flip_x = args.flip_x,
|
.flip_x = args.flip_x,
|
||||||
.flip_y = args.flip_y,
|
.flip_y = args.flip_y,
|
||||||
@ -100,6 +89,7 @@ void push_circle(t_fvec2 position, float radius, t_color color) {
|
|||||||
|
|
||||||
|
|
||||||
/* TODO: automatic handling of repeating textures */
|
/* TODO: automatic handling of repeating textures */
|
||||||
|
/* for that we could allocate a loner texture */
|
||||||
void unfurl_triangle(const char *path,
|
void unfurl_triangle(const char *path,
|
||||||
t_fvec3 v0,
|
t_fvec3 v0,
|
||||||
t_fvec3 v1,
|
t_fvec3 v1,
|
||||||
@ -108,70 +98,35 @@ void unfurl_triangle(const char *path,
|
|||||||
t_shvec2 uv1,
|
t_shvec2 uv1,
|
||||||
t_shvec2 uv2)
|
t_shvec2 uv2)
|
||||||
{
|
{
|
||||||
/* corrected atlas texture coordinates */
|
const t_texture_key texture_key = textures_get_key(&ctx.texture_cache, path);
|
||||||
t_fvec2 uv0c, uv1c, uv2c;
|
|
||||||
struct mesh_batch *batch_p;
|
|
||||||
|
|
||||||
textures_load(&ctx.texture_cache, path);
|
struct mesh_batch_item *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
|
||||||
const SDL_Rect srcrect = textures_get_srcrect(&ctx.texture_cache, path);
|
if (!batch_p) {
|
||||||
|
struct mesh_batch item = {0};
|
||||||
const int atlas_index = textures_get_atlas_index(&ctx.texture_cache, path);
|
hmput(ctx.uncolored_mesh_batches, texture_key, item);
|
||||||
if (atlas_index == -1) {
|
batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */
|
||||||
uv0c.x = (float)uv0.x / (float)srcrect.w;
|
|
||||||
uv0c.y = (float)uv0.y / (float)srcrect.h;
|
|
||||||
uv1c.x = (float)uv1.x / (float)srcrect.w;
|
|
||||||
uv1c.y = (float)uv1.y / (float)srcrect.h;
|
|
||||||
uv2c.x = (float)uv2.x / (float)srcrect.w;
|
|
||||||
uv2c.y = (float)uv2.y / (float)srcrect.h;
|
|
||||||
|
|
||||||
batch_p = &shgetp(ctx.uncolored_mesh_batches_loners, path)->value;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const size_t old_len = arrlenu(ctx.uncolored_mesh_batches);
|
|
||||||
if ((size_t)atlas_index + 1 >= old_len) {
|
|
||||||
/* grow to accommodate texture cache atlases */
|
|
||||||
arrsetlen(ctx.uncolored_mesh_batches, atlas_index + 1);
|
|
||||||
|
|
||||||
/* zero initialize it all, it's a valid state */
|
|
||||||
SDL_memset(&ctx.uncolored_mesh_batches[atlas_index],
|
|
||||||
0,
|
|
||||||
sizeof (struct mesh_batch) * ((atlas_index + 1) - old_len));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const float wr = (float)srcrect.w / TEXTURE_ATLAS_SIZE;
|
union uncolored_space_triangle triangle = { .primitive = {
|
||||||
const float hr = (float)srcrect.h / TEXTURE_ATLAS_SIZE;
|
|
||||||
const float xr = (float)srcrect.x / TEXTURE_ATLAS_SIZE;
|
|
||||||
const float yr = (float)srcrect.y / TEXTURE_ATLAS_SIZE;
|
|
||||||
|
|
||||||
uv0c.x = xr + ((float)uv0.x / (float)srcrect.w) * wr;
|
|
||||||
uv0c.y = yr + ((float)uv0.y / (float)srcrect.h) * hr;
|
|
||||||
uv1c.x = xr + ((float)uv1.x / (float)srcrect.w) * wr;
|
|
||||||
uv1c.y = yr + ((float)uv1.y / (float)srcrect.h) * hr;
|
|
||||||
uv2c.x = xr + ((float)uv2.x / (float)srcrect.w) * wr;
|
|
||||||
uv2c.y = yr + ((float)uv2.y / (float)srcrect.h) * hr;
|
|
||||||
|
|
||||||
batch_p = &ctx.uncolored_mesh_batches[atlas_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
struct uncolored_space_triangle *data = (struct uncolored_space_triangle *)batch_p->data;
|
|
||||||
struct uncolored_space_triangle pack = {
|
|
||||||
.v0 = v0,
|
.v0 = v0,
|
||||||
.uv0 = uv0c,
|
|
||||||
.v1 = v1,
|
.v1 = v1,
|
||||||
.uv1 = uv1c,
|
|
||||||
.v2 = v2,
|
.v2 = v2,
|
||||||
.uv2 = uv2c,
|
.uv1 = m_to_fvec2(uv1),
|
||||||
};
|
.uv0 = m_to_fvec2(uv0),
|
||||||
arrpush(data, pack);
|
.uv2 = m_to_fvec2(uv2),
|
||||||
|
}};
|
||||||
|
|
||||||
batch_p->data = (uint8_t *)data;
|
union uncolored_space_triangle *triangles =
|
||||||
|
(union uncolored_space_triangle *)batch_p->value.primitives;
|
||||||
|
arrpush(triangles, triangle);
|
||||||
|
batch_p->value.primitives = (uint8_t *)triangles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* compare functions for the sort in render_sprites */
|
/* compare functions for the sort in render_sprites */
|
||||||
static int cmp_atlases(const void *a, const void *b) {
|
static int cmp_atlases(const void *a, const void *b) {
|
||||||
int index_a = ((const struct sprite_primitive *)a)->atlas_index;
|
int index_a = ((const struct sprite_primitive *)a)->texture_key.id;
|
||||||
int index_b = ((const struct sprite_primitive *)b)->atlas_index;
|
int index_b = ((const struct sprite_primitive *)b)->texture_key.id;
|
||||||
|
|
||||||
return (index_a > index_b) - (index_a < index_b);
|
return (index_a > index_b) - (index_a < index_b);
|
||||||
}
|
}
|
||||||
@ -259,40 +214,28 @@ static void render_sprite(struct sprite_primitive *sprite) {
|
|||||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||||
glClientActiveTexture(GL_TEXTURE0);
|
glClientActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
/* loner */
|
textures_bind(&ctx.texture_cache, sprite->texture_key, GL_TEXTURE_2D);
|
||||||
if (sprite->atlas_index == -1) {
|
|
||||||
textures_bind_loner(&ctx.texture_cache, sprite->path, GL_TEXTURE_2D);
|
t_rect srcrect = textures_get_srcrect(&ctx.texture_cache, sprite->texture_key);
|
||||||
glTexCoordPointer(2,
|
t_rect dims = textures_get_dims(&ctx.texture_cache, sprite->texture_key);
|
||||||
GL_SHORT,
|
|
||||||
0,
|
|
||||||
(void *)(int16_t[6 * 2]) {
|
|
||||||
0, INT16_MAX,
|
|
||||||
0, 0,
|
|
||||||
INT16_MAX, 0,
|
|
||||||
INT16_MAX, 0,
|
|
||||||
INT16_MAX, INT16_MAX,
|
|
||||||
0, INT16_MAX });
|
|
||||||
} else {
|
|
||||||
SDL_Rect srcrect = textures_get_srcrect(&ctx.texture_cache, sprite->path);
|
|
||||||
textures_bind_atlas(&ctx.texture_cache, sprite->atlas_index, GL_TEXTURE_2D);
|
|
||||||
glTexCoordPointer(2,
|
glTexCoordPointer(2,
|
||||||
GL_FLOAT,
|
GL_FLOAT,
|
||||||
0,
|
0,
|
||||||
/* TODO: try using shorts */
|
/* TODO: try using shorts */
|
||||||
(void *)(float[6 * 2]) {
|
(void *)(float[6 * 2]) {
|
||||||
(float)srcrect.x / TEXTURE_ATLAS_SIZE,
|
(float)srcrect.x / (float)dims.w,
|
||||||
(float)srcrect.y / TEXTURE_ATLAS_SIZE,
|
(float)srcrect.y / (float)dims.h,
|
||||||
(float)srcrect.x / TEXTURE_ATLAS_SIZE,
|
(float)srcrect.x / (float)dims.w,
|
||||||
(float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE,
|
(float)(srcrect.y + srcrect.h) / (float)dims.h,
|
||||||
(float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE,
|
(float)(srcrect.x + srcrect.w) / (float)dims.w,
|
||||||
(float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE,
|
(float)(srcrect.y + srcrect.h) / (float)dims.h,
|
||||||
(float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE,
|
(float)(srcrect.x + srcrect.w) / (float)dims.w,
|
||||||
(float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE,
|
(float)(srcrect.y + srcrect.h) / (float)dims.h,
|
||||||
(float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE,
|
(float)(srcrect.x + srcrect.w) / (float)dims.w,
|
||||||
(float)srcrect.y / TEXTURE_ATLAS_SIZE,
|
(float)srcrect.y / (float)dims.h,
|
||||||
(float)srcrect.x / TEXTURE_ATLAS_SIZE,
|
(float)srcrect.x / (float)dims.w,
|
||||||
(float)srcrect.y / TEXTURE_ATLAS_SIZE });
|
(float)srcrect.y / (float)dims.h });
|
||||||
}
|
|
||||||
|
|
||||||
glColor4ub(sprite->color.r, sprite->color.g, sprite->color.b, sprite->color.a);
|
glColor4ub(sprite->color.r, sprite->color.g, sprite->color.b, sprite->color.a);
|
||||||
|
|
||||||
@ -446,41 +389,69 @@ static void render_circles(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void draw_uncolored_space_traingle_batch(struct mesh_batch *batch) {
|
static void draw_uncolored_space_traingle_batch(struct mesh_batch *batch,
|
||||||
size_t data_len = arrlenu(batch->data);
|
t_texture_key texture_key)
|
||||||
|
{
|
||||||
|
size_t primitives_len = arrlenu(batch->primitives);
|
||||||
|
|
||||||
|
if (primitives_len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
/* create vertex array object */
|
/* create vertex array object */
|
||||||
if (batch->buffer == 0)
|
if (batch->buffer == 0)
|
||||||
glGenBuffers(1, &batch->buffer);
|
glGenBuffers(1, &batch->buffer);
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, batch->buffer);
|
|
||||||
|
|
||||||
/* TODO: try using mapped buffers while building batches instead? */
|
/* TODO: try using mapped buffers while building batches instead? */
|
||||||
/* this way we could skip client side copy that is kept until commitment */
|
/* this way we could skip client side copy that is kept until commitment */
|
||||||
/* alternatively we could commit glBufferSubData based on a threshold */
|
/* 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;
|
||||||
|
|
||||||
|
t_rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
|
||||||
|
t_rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
|
||||||
|
|
||||||
|
const float wr = (float)srcrect.w / (float)dims.w;
|
||||||
|
const float hr = (float)srcrect.h / (float)dims.h;
|
||||||
|
const float xr = (float)srcrect.x / (float)dims.w;
|
||||||
|
const float yr = (float)srcrect.y / (float)dims.h;
|
||||||
|
|
||||||
|
payload->uv0.x = xr + ((float)payload->uv0.x / (float)srcrect.w) * wr;
|
||||||
|
payload->uv0.y = yr + ((float)payload->uv0.y / (float)srcrect.h) * hr;
|
||||||
|
payload->uv1.x = xr + ((float)payload->uv1.x / (float)srcrect.w) * wr;
|
||||||
|
payload->uv1.y = yr + ((float)payload->uv1.y / (float)srcrect.h) * hr;
|
||||||
|
payload->uv2.x = xr + ((float)payload->uv2.x / (float)srcrect.w) * wr;
|
||||||
|
payload->uv2.y = yr + ((float)payload->uv2.y / (float)srcrect.h) * hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
textures_bind(&ctx.texture_cache, texture_key, GL_TEXTURE_2D);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, batch->buffer);
|
||||||
|
|
||||||
/* upload batched data */
|
/* upload batched data */
|
||||||
glBufferData(GL_ARRAY_BUFFER,
|
glBufferData(GL_ARRAY_BUFFER,
|
||||||
data_len * sizeof (struct uncolored_space_triangle),
|
primitives_len * sizeof (struct uncolored_space_triangle_payload),
|
||||||
batch->data,
|
batch->primitives,
|
||||||
GL_STREAM_DRAW);
|
GL_STREAM_DRAW);
|
||||||
|
|
||||||
/* vertex specification*/
|
/* vertex specification*/
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
glEnableClientState(GL_VERTEX_ARRAY);
|
||||||
glVertexPointer(3,
|
glVertexPointer(3,
|
||||||
GL_FLOAT,
|
GL_FLOAT,
|
||||||
offsetof(struct uncolored_space_triangle, v1),
|
offsetof(struct uncolored_space_triangle_payload, v1),
|
||||||
(void *)offsetof(struct uncolored_space_triangle, v0));
|
(void *)offsetof(struct uncolored_space_triangle_payload, v0));
|
||||||
|
|
||||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||||
glClientActiveTexture(GL_TEXTURE0);
|
glClientActiveTexture(GL_TEXTURE0);
|
||||||
glTexCoordPointer(2,
|
glTexCoordPointer(2,
|
||||||
GL_FLOAT,
|
GL_FLOAT,
|
||||||
offsetof(struct uncolored_space_triangle, v1),
|
offsetof(struct uncolored_space_triangle_payload, v1),
|
||||||
(void *)offsetof(struct uncolored_space_triangle, uv0));
|
(void *)offsetof(struct uncolored_space_triangle_payload, uv0));
|
||||||
|
|
||||||
/* commit for drawing */
|
/* commit for drawing */
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3 * (int)data_len);
|
glDrawArrays(GL_TRIANGLES, 0, 3 * (int)primitives_len);
|
||||||
|
|
||||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||||
glDisableClientState(GL_VERTEX_ARRAY);
|
glDisableClientState(GL_VERTEX_ARRAY);
|
||||||
@ -498,28 +469,15 @@ static void render_space(void) {
|
|||||||
/* solid white, no modulation */
|
/* solid white, no modulation */
|
||||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
for (size_t i = 0; i < arrlenu(ctx.uncolored_mesh_batches); ++i) {
|
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
|
||||||
if (arrlenu(&ctx.uncolored_mesh_batches[i].data) > 0) {
|
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
|
||||||
textures_bind_atlas(&ctx.texture_cache, (int)i, GL_TEXTURE_2D);
|
ctx.uncolored_mesh_batches[i].key);
|
||||||
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < shlenu(ctx.uncolored_mesh_batches_loners); ++i) {
|
|
||||||
if (arrlenu(&ctx.uncolored_mesh_batches_loners[i].value.data) > 0) {
|
|
||||||
textures_bind_loner(&ctx.texture_cache,
|
|
||||||
ctx.uncolored_mesh_batches_loners[i].key,
|
|
||||||
GL_TEXTURE_2D);
|
|
||||||
|
|
||||||
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches_loners[i].value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void render(void) {
|
void render(void) {
|
||||||
if (ctx.texture_cache.is_dirty)
|
textures_update_atlas(&ctx.texture_cache);
|
||||||
textures_update_current_atlas(&ctx.texture_cache);
|
|
||||||
|
|
||||||
/* fit rendering context onto the resizable screen */
|
/* fit rendering context onto the resizable screen */
|
||||||
if ((float)ctx.window_w / (float)ctx.window_h > RENDER_BASE_RATIO) {
|
if ((float)ctx.window_w / (float)ctx.window_h > RENDER_BASE_RATIO) {
|
||||||
|
136
src/textures.c
136
src/textures.c
@ -10,7 +10,6 @@
|
|||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
#include <stb_rect_pack.h>
|
#include <stb_rect_pack.h>
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -139,7 +138,12 @@ static void recreate_current_atlas_texture(struct texture_cache *cache) {
|
|||||||
SDL_BlitSurface(cache->hash[i].value.data,
|
SDL_BlitSurface(cache->hash[i].value.data,
|
||||||
NULL,
|
NULL,
|
||||||
atlas_surface,
|
atlas_surface,
|
||||||
&cache->hash[i].value.srcrect);
|
&(SDL_Rect){
|
||||||
|
.x = cache->hash[i].value.srcrect.x,
|
||||||
|
.y = cache->hash[i].value.srcrect.y,
|
||||||
|
.w = cache->hash[i].value.srcrect.w,
|
||||||
|
.h = cache->hash[i].value.srcrect.h,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* texturize it! */
|
/* texturize it! */
|
||||||
@ -212,7 +216,7 @@ static bool update_rects(struct texture_cache *cache, stbrp_rect *rects, stbrp_r
|
|||||||
/* updates the atlas location of every rect in the cache */
|
/* updates the atlas location of every rect in the cache */
|
||||||
static void update_texture_rects_in_atlas(struct texture_cache *cache, stbrp_rect *rects) {
|
static void update_texture_rects_in_atlas(struct texture_cache *cache, stbrp_rect *rects) {
|
||||||
for (size_t i = 0; i < arrlenu(rects); ++i) {
|
for (size_t i = 0; i < arrlenu(rects); ++i) {
|
||||||
cache->hash[i].value.srcrect = (SDL_Rect) {
|
cache->hash[i].value.srcrect = (t_rect) {
|
||||||
.x = rects[i].x,
|
.x = rects[i].x,
|
||||||
.y = rects[i].y,
|
.y = rects[i].y,
|
||||||
.w = rects[i].w,
|
.w = rects[i].w,
|
||||||
@ -225,7 +229,6 @@ static void update_texture_rects_in_atlas(struct texture_cache *cache, stbrp_rec
|
|||||||
void textures_cache_init(struct texture_cache *cache, SDL_Window *window) {
|
void textures_cache_init(struct texture_cache *cache, SDL_Window *window) {
|
||||||
cache->window = window;
|
cache->window = window;
|
||||||
sh_new_arena(cache->hash);
|
sh_new_arena(cache->hash);
|
||||||
sh_new_arena(cache->loner_hash);
|
|
||||||
|
|
||||||
cache->node_buffer = cmalloc(sizeof *cache->node_buffer * TEXTURE_ATLAS_SIZE);
|
cache->node_buffer = cmalloc(sizeof *cache->node_buffer * TEXTURE_ATLAS_SIZE);
|
||||||
|
|
||||||
@ -253,11 +256,6 @@ void textures_cache_deinit(struct texture_cache *cache) {
|
|||||||
}
|
}
|
||||||
shfree(cache->hash);
|
shfree(cache->hash);
|
||||||
|
|
||||||
for (size_t i = 0; i < shlenu(cache->loner_hash); ++i) {
|
|
||||||
SDL_FreeSurface(cache->loner_hash[i].value.data);
|
|
||||||
}
|
|
||||||
shfree(cache->loner_hash);
|
|
||||||
|
|
||||||
free(cache->node_buffer);
|
free(cache->node_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,38 +280,38 @@ void textures_dump_atlases(struct texture_cache *cache) {
|
|||||||
IMG_SavePNG_RW(cache->atlas_surfaces[i], handle, true);
|
IMG_SavePNG_RW(cache->atlas_surfaces[i], handle, true);
|
||||||
log_info("Dumped atlas %s", buf);
|
log_info("Dumped atlas %s", buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t num_loners = shlenu(cache->loner_hash);
|
|
||||||
log_info("%zd atlases dumped. %zd loners left undumped.", i, num_loners);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void textures_load(struct texture_cache *cache, const char *path) {
|
static t_texture_key textures_load(struct texture_cache *cache, const char *path) {
|
||||||
/* no need to do anything if it was loaded already */
|
/* no need to do anything if it was loaded already */
|
||||||
if (shgeti(cache->hash, path) >= 0 || shgeti(cache->loner_hash, path) >= 0)
|
if (shgeti(cache->hash, path) >= 0)
|
||||||
return;
|
return (t_texture_key){0};
|
||||||
|
|
||||||
SDL_Surface *surface = image_to_surface(path);
|
SDL_Surface *surface = image_to_surface(path);
|
||||||
struct texture new_texture;
|
struct texture new_texture = {0};
|
||||||
new_texture.data = surface;
|
new_texture.data = surface;
|
||||||
|
|
||||||
/* it's a "loner texture," it doesn't fit in an atlas so it's not in one */
|
/* 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) {
|
if (surface->w > TEXTURE_ATLAS_SIZE || surface->h > TEXTURE_ATLAS_SIZE) {
|
||||||
new_texture.loner_data = new_gl_texture();
|
new_texture.loner_texture = new_gl_texture();
|
||||||
upload_texture_from_surface(new_texture.loner_data, surface);
|
upload_texture_from_surface(new_texture.loner_texture, surface);
|
||||||
new_texture.atlas_index = -1;
|
new_texture.srcrect = (t_rect) { .w = surface->w, .h = surface->h };
|
||||||
new_texture.srcrect = (SDL_Rect) {
|
shput(cache->hash, path, new_texture);
|
||||||
.w = surface->w, .h = surface->h };
|
return (t_texture_key){ (int)shgeti(cache->hash, path) + 1 };
|
||||||
shput(cache->loner_hash, path, new_texture);
|
|
||||||
} else {
|
} else {
|
||||||
new_texture.atlas_index = cache->atlas_index;
|
new_texture.atlas_index = cache->atlas_index;
|
||||||
shput(cache->hash, path, new_texture);
|
shput(cache->hash, path, new_texture);
|
||||||
cache->is_dirty = true;
|
cache->is_dirty = true;
|
||||||
|
return (t_texture_key){ (int)shgeti(cache->hash, path) + 1 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void textures_update_current_atlas(struct texture_cache *cache) {
|
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 */
|
/* this function makes a lot more sense if you read stb_rect_pack.h */
|
||||||
stbrp_context pack_ctx; /* target info */
|
stbrp_context pack_ctx; /* target info */
|
||||||
stbrp_init_target(&pack_ctx,
|
stbrp_init_target(&pack_ctx,
|
||||||
@ -355,68 +353,56 @@ void textures_update_current_atlas(struct texture_cache *cache) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SDL_Rect textures_get_srcrect(struct texture_cache *cache, const char *path) {
|
t_texture_key textures_get_key(struct texture_cache *cache, const char *path) {
|
||||||
int index = textures_get_atlas_index(cache, path);
|
/* hash tables are assumed to be stable, so we just return indices */
|
||||||
if (index == -1) {
|
ptrdiff_t texture = shgeti(cache->hash, path);
|
||||||
return shget(cache->loner_hash, path).srcrect;
|
|
||||||
} else if (index == INT_MIN) {
|
/* load it if it isn't */
|
||||||
|
if (texture == -1) {
|
||||||
|
return textures_load(cache, path);
|
||||||
|
} else
|
||||||
|
return (t_texture_key){ (int)texture + 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
t_rect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key) {
|
||||||
|
if (key.id != 0) {
|
||||||
|
return cache->hash[key.id - 1].value.srcrect;
|
||||||
|
} else {
|
||||||
CRY("Texture lookup failed.",
|
CRY("Texture lookup failed.",
|
||||||
"Tried to get texture that isn't loaded.");
|
"Tried to get texture that isn't loaded.");
|
||||||
return (SDL_Rect){ 0, 0, 0, 0 };
|
return (t_rect){ 0, 0, 0, 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
t_rect textures_get_dims(const struct texture_cache *cache, t_texture_key key) {
|
||||||
|
if (key.id != 0) {
|
||||||
|
if (cache->hash[key.id - 1].value.loner_texture != 0)
|
||||||
|
return cache->hash[key.id - 1].value.srcrect;
|
||||||
|
else
|
||||||
|
return (t_rect){ .w = TEXTURE_ATLAS_SIZE, .h = TEXTURE_ATLAS_SIZE };
|
||||||
} else {
|
} else {
|
||||||
return shget(cache->hash, path).srcrect;
|
CRY("Texture lookup failed.",
|
||||||
|
"Tried to get texture that isn't loaded.");
|
||||||
|
return (t_rect){ 0, 0, 0, 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* TODO: instead of index, return a 'key' with following encoded meaning: */
|
void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target) {
|
||||||
/* value of 0 - no atlas (#define NO_ATLAS (0) ?) */
|
if (key.id != 0) {
|
||||||
/* negative value - index in loners (-key - 1) */
|
if (cache->hash[key.id - 1].value.loner_texture == 0)
|
||||||
/* positive value - index in atlases (key - 1) */
|
glBindTexture(target, cache->atlas_textures[cache->hash[key.id - 1].value.atlas_index]);
|
||||||
int textures_get_atlas_index(struct texture_cache *cache, const char *path) {
|
else
|
||||||
struct texture_cache_item *texture = shgetp_null(cache->hash, path);
|
glBindTexture(target, cache->hash[key.id - 1].value.loner_texture);
|
||||||
|
} else if (key.id == 0) {
|
||||||
/* it might be a loner texture */
|
CRY("Texture binding failed.",
|
||||||
if (texture == NULL) {
|
"Tried to get texture that isn't loaded.");
|
||||||
texture = shgetp_null(cache->loner_hash, path);
|
|
||||||
|
|
||||||
/* never mind it's just not there at all */
|
|
||||||
if (texture == NULL) {
|
|
||||||
CRY("Texture atlas index lookup failed.",
|
|
||||||
"Tried to get atlas index of texture that isn't loaded.");
|
|
||||||
return INT_MIN;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return texture->value.atlas_index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void textures_bind_atlas(struct texture_cache *cache, int index, GLenum target) {
|
size_t textures_get_num_atlases(const struct texture_cache *cache) {
|
||||||
/* out of bounds */
|
|
||||||
if (arrlen(cache->atlas_textures) < index + 1 || index < 0) {
|
|
||||||
CRY("Atlas texture binding failed.",
|
|
||||||
"Tried to bind texture by invalid index");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindTexture(target, cache->atlas_textures[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void textures_bind_loner(struct texture_cache *cache, const char *path, GLenum target) {
|
|
||||||
struct texture_cache_item *texture = shgetp_null(cache->loner_hash, path);
|
|
||||||
|
|
||||||
if (texture == NULL) {
|
|
||||||
CRY("Loner texture binding failed.",
|
|
||||||
"Tried to bind texture that isn't loaded.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindTexture(target, texture->value.loner_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t textures_get_num_atlases(struct texture_cache *cache) {
|
|
||||||
return cache->atlas_index + 1;
|
return cache->atlas_index + 1;
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,20 @@
|
|||||||
#define TEXTURES_H
|
#define TEXTURES_H
|
||||||
|
|
||||||
#include "private/textures.h"
|
#include "private/textures.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
/* type safe structure for persistent texture handles */
|
||||||
|
typedef struct { int id; } t_texture_key;
|
||||||
|
|
||||||
|
/* tests whether given key structure corresponds to any texture */
|
||||||
|
#define m_texture_key_is_valid(p_key) ((p_key).id != 0)
|
||||||
|
|
||||||
|
/* tests whether given key corresponds to atlas texture or a loner */
|
||||||
|
#define m_texture_is_atlas(p_key) ((p_key).id > 0)
|
||||||
|
|
||||||
void textures_cache_init(struct texture_cache *cache, SDL_Window *window);
|
void textures_cache_init(struct texture_cache *cache, SDL_Window *window);
|
||||||
void textures_cache_deinit(struct texture_cache *cache);
|
void textures_cache_deinit(struct texture_cache *cache);
|
||||||
|
|
||||||
@ -15,25 +25,27 @@ void textures_dump_atlases(struct texture_cache *cache);
|
|||||||
/* loads an image if it isn't in the cache, otherwise a no-op. */
|
/* 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 */
|
/* can be called from anywhere at any time after init, useful if you want to */
|
||||||
/* preload textures you know will definitely be used */
|
/* preload textures you know will definitely be used */
|
||||||
void textures_load(struct texture_cache *cache, const char *path);
|
// void textures_load(struct texture_cache *cache, const char *path);
|
||||||
|
|
||||||
/* repacks the current texture atlas based on the texture cache */
|
/* repacks the current texture atlas based on the texture cache if needed */
|
||||||
void textures_update_current_atlas(struct texture_cache *cache);
|
/* 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 rect in a texture cache atlas based on a path, for drawing */
|
/* returns a persistent handle to some texture in cache, loading it if needed */
|
||||||
/* if the texture is not found, returns a zero-filled rect (so check w or h) */
|
/* check the result with m_texture_key_is_valid() */
|
||||||
SDL_Rect textures_get_srcrect(struct texture_cache *cache, const char *path);
|
t_texture_key textures_get_key(struct texture_cache *cache, const char *path);
|
||||||
|
|
||||||
/* returns which atlas the texture in the path is in, starting from 0 */
|
/* returns a rect in a texture cache of the given key */
|
||||||
/* if the texture is not found, returns INT_MIN */
|
t_rect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key);
|
||||||
int textures_get_atlas_index(struct texture_cache *cache, const char *path);
|
|
||||||
|
/* returns a rect of dimensions of the whole texture (whole atlas) */
|
||||||
|
t_rect textures_get_dims(const struct texture_cache *cache, t_texture_key key);
|
||||||
|
|
||||||
/* binds atlas texture in opengl state */
|
/* binds atlas texture in opengl state */
|
||||||
void textures_bind_atlas(struct texture_cache *cache, int index, GLenum target);
|
void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target);
|
||||||
|
|
||||||
void textures_bind_loner(struct texture_cache *cache, const char *path, GLenum target);
|
|
||||||
|
|
||||||
/* returns the number of atlases in the cache */
|
/* returns the number of atlases in the cache */
|
||||||
size_t textures_get_num_atlases(struct texture_cache *cache);
|
size_t textures_get_num_atlases(const struct texture_cache *cache);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
15
src/util.c
15
src/util.c
@ -186,6 +186,21 @@ t_frect to_frect(t_rect rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
t_fvec2 fvec2_from_vec2(t_vec2 vec) {
|
||||||
|
return (t_fvec2) {
|
||||||
|
.x = (float)vec.x,
|
||||||
|
.y = (float)vec.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
t_fvec2 fvec2_from_shvec2(t_shvec2 vec) {
|
||||||
|
return (t_fvec2) {
|
||||||
|
.x = (float)vec.x,
|
||||||
|
.y = (float)vec.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void tick_timer(int *value) {
|
void tick_timer(int *value) {
|
||||||
*value = MAX(*value - 1, 0);
|
*value = MAX(*value - 1, 0);
|
||||||
}
|
}
|
||||||
|
15
src/util.h
15
src/util.h
@ -48,7 +48,9 @@ void *ccalloc(size_t num, size_t size);
|
|||||||
#define MAX SDL_max
|
#define MAX SDL_max
|
||||||
#define MIN SDL_min
|
#define MIN SDL_min
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
#define M_PI 3.14159265358979323846264338327950288 /**< pi */
|
#define M_PI 3.14159265358979323846264338327950288 /**< pi */
|
||||||
|
#endif
|
||||||
|
|
||||||
/* sets buf_out to a pointer to a byte buffer which must be freed. */
|
/* sets buf_out to a pointer to a byte buffer which must be freed. */
|
||||||
/* returns the size of this buffer. */
|
/* returns the size of this buffer. */
|
||||||
@ -94,6 +96,7 @@ typedef struct frect {
|
|||||||
|
|
||||||
bool intersect_frect(const t_frect *a, const t_frect *b, t_frect *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_frect to_frect(t_rect rect);
|
||||||
|
|
||||||
|
|
||||||
@ -122,6 +125,17 @@ typedef struct shvec2 {
|
|||||||
} t_shvec2;
|
} t_shvec2;
|
||||||
|
|
||||||
|
|
||||||
|
/* aren't macros to prevent double evaluation with side effects */
|
||||||
|
/* maybe could be inlined? i hope LTO will resolve this */
|
||||||
|
t_fvec2 fvec2_from_vec2(t_vec2 vec);
|
||||||
|
t_fvec2 fvec2_from_shvec2(t_shvec2 vec);
|
||||||
|
|
||||||
|
#define m_to_fvec2(p_any_vec2) (_Generic((p_any_vec2), \
|
||||||
|
t_vec2: fvec2_from_vec2, \
|
||||||
|
t_shvec2: fvec2_from_shvec2 \
|
||||||
|
)(p_any_vec2))
|
||||||
|
|
||||||
|
|
||||||
/* decrements an lvalue (which should be an int), stopping at 0 */
|
/* decrements an lvalue (which should be an int), stopping at 0 */
|
||||||
/* meant for tick-based timers in game logic */
|
/* meant for tick-based timers in game logic */
|
||||||
/*
|
/*
|
||||||
@ -130,7 +144,6 @@ typedef struct shvec2 {
|
|||||||
*/
|
*/
|
||||||
void tick_timer(int *value);
|
void tick_timer(int *value);
|
||||||
|
|
||||||
|
|
||||||
/* decrements a floating point second-based timer, stopping at 0.0 */
|
/* decrements a floating point second-based timer, stopping at 0.0 */
|
||||||
/* meant for poll based real time logic in game logic */
|
/* meant for poll based real time logic in game logic */
|
||||||
/* note that it should be decremented only on the next tick after its creation */
|
/* note that it should be decremented only on the next tick after its creation */
|
||||||
|
Loading…
Reference in New Issue
Block a user