rework of textures, finalization of basic opengl rendering

This commit is contained in:
veclav talica 2024-07-14 16:04:12 +03:00
parent 55d85399e9
commit bd53a931c0
10 changed files with 243 additions and 245 deletions

2
.clangd Normal file
View File

@ -0,0 +1,2 @@
CompileFlags:
CompilationDatabase: "./.build/"

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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 */

View File

@ -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) {

View File

@ -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;
}
size_t textures_get_num_atlases(const struct texture_cache *cache) {
void textures_bind_atlas(struct texture_cache *cache, int index, GLenum target) {
/* 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;
} }

View File

@ -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

View File

@ -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);
} }

View File

@ -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 */