From bd53a931c00708d8c78f7a382620e94b8beeaae3 Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Sun, 14 Jul 2024 16:04:12 +0300 Subject: [PATCH] rework of textures, finalization of basic opengl rendering --- .clangd | 2 + CMakeLists.txt | 2 +- src/context.h | 4 +- src/private/rendering.h | 45 ++++++--- src/private/textures.h | 15 +-- src/rendering.c | 216 ++++++++++++++++------------------------ src/textures.c | 136 ++++++++++++------------- src/textures.h | 38 ++++--- src/util.c | 15 +++ src/util.h | 15 ++- 10 files changed, 243 insertions(+), 245 deletions(-) create mode 100644 .clangd diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..c835fb9 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: "./.build/" diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a427f3..5dd55ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_FILES}) set_target_properties(${PROJECT_NAME} PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON - C_EXTENSIONS OFF) + C_EXTENSIONS ON) # extensions are required by stb_ds.h # distribution definitions set(ORGANIZATION_NAME "wanp" CACHE STRING diff --git a/src/context.h b/src/context.h index bf31e21..d26746f 100644 --- a/src/context.h +++ b/src/context.h @@ -23,9 +23,7 @@ typedef struct context { struct sprite_primitive *render_queue_sprites; struct rect_primitive *render_queue_rectangles; struct circle_primitive *render_queue_circles; - - struct mesh_batch *uncolored_mesh_batches; /* texture_cache reflected */ - struct mesh_batch_item *uncolored_mesh_batches_loners; /* path reflected */ + struct mesh_batch_item *uncolored_mesh_batches; struct audio_channel_item *audio_channels; SDL_AudioDeviceID audio_device; diff --git a/src/private/rendering.h b/src/private/rendering.h index 75b662f..5c3326e 100644 --- a/src/private/rendering.h +++ b/src/private/rendering.h @@ -2,6 +2,7 @@ #define PRIVATE_RENDERING_H #include "../rendering.h" +#include "../textures.h" #include "../util.h" #include @@ -13,9 +14,8 @@ struct sprite_primitive { t_frect rect; t_color color; double rotation; - char *path; SDL_BlendMode blend_mode; - int atlas_index; + t_texture_key texture_key; int layer; bool flip_x; bool flip_y; @@ -32,26 +32,39 @@ struct circle_primitive { t_fvec2 position; }; +/* 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; + + /* 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 *data; /* client side storage */ - // size_t buffer_len; /* element count */ + GLuint buffer; /* server side storage */ + size_t buffer_len; /* element count */ + uint8_t *primitives; }; struct mesh_batch_item { - char *key; + t_texture_key key; struct mesh_batch value; }; -/* is structure that is in opengl vertex array */ -struct uncolored_space_triangle { - t_fvec3 v0; - t_fvec2 uv0; - t_fvec3 v1; - t_fvec2 uv1; - t_fvec3 v2; - t_fvec2 uv2; -}; - #endif diff --git a/src/private/textures.h b/src/private/textures.h index 87b49d4..f7abb32 100644 --- a/src/private/textures.h +++ b/src/private/textures.h @@ -1,6 +1,8 @@ #ifndef PRIVATE_TEXTURES_H #define PRIVATE_TEXTURES_H +#include "../util.h" + #include #include #include @@ -8,10 +10,10 @@ #include struct texture { - SDL_Rect srcrect; /* position in atlas */ + t_rect srcrect; /* position in atlas */ SDL_Surface *data; /* original image data */ - GLuint loner_data; /* loner textures store their data directly */ - int atlas_index; /* which atlas the texture is in */ + int atlas_index; + GLuint loner_texture; /* stored directly for loners, == 0 means atlas_index should be used*/ int8_t layer; }; @@ -27,15 +29,14 @@ struct texture_cache { SDL_Window *window; 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; - GLuint *atlas_textures; + GLuint *atlas_textures; /* shared by atlas textures */ int atlas_index; - bool is_dirty; /* current atlas needs to be recreated */ + bool is_dirty; /* current atlas needs to be recreated */ }; #endif diff --git a/src/rendering.c b/src/rendering.c index 508835a..e9d5786 100644 --- a/src/rendering.c +++ b/src/rendering.c @@ -20,11 +20,8 @@ void render_queue_clear(void) { arrsetlen(ctx.render_queue_rectangles, 0); arrsetlen(ctx.render_queue_circles, 0); - for (size_t i = 0; i < arrlenu(ctx.uncolored_mesh_batches); ++i) - arrsetlen(ctx.uncolored_mesh_batches[i].data, 0); - - for (size_t i = 0; i < shlenu(ctx.uncolored_mesh_batches_loners); ++i) - arrsetlen(ctx.uncolored_mesh_batches_loners[i].value.data, 0); + for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) + arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0); } @@ -37,16 +34,12 @@ void render_queue_clear(void) { /* sprite */ void push_sprite(char *path, t_frect rect) { - textures_load(&ctx.texture_cache, path); - struct sprite_primitive sprite = { .rect = rect, .color = (t_color) { 255, 255, 255, 255 }, - .path = path, .rotation = 0.0, .blend_mode = SDL_BLENDMODE_BLEND, - .atlas_index = - textures_get_atlas_index(&ctx.texture_cache, path), + .texture_key = textures_get_key(&ctx.texture_cache, path), .layer = 0, .flip_x = 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) { - textures_load(&ctx.texture_cache, args.path); - struct sprite_primitive sprite = { .rect = rect, .color = args.color, - .path = args.path, .rotation = args.rotation, .blend_mode = args.blend_mode, - .atlas_index = - textures_get_atlas_index(&ctx.texture_cache, args.path), + .texture_key = textures_get_key(&ctx.texture_cache, args.path), .layer = args.layer, .flip_x = args.flip_x, .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 */ +/* for that we could allocate a loner texture */ void unfurl_triangle(const char *path, t_fvec3 v0, t_fvec3 v1, @@ -108,70 +98,35 @@ void unfurl_triangle(const char *path, t_shvec2 uv1, t_shvec2 uv2) { - /* corrected atlas texture coordinates */ - t_fvec2 uv0c, uv1c, uv2c; - struct mesh_batch *batch_p; + const t_texture_key texture_key = textures_get_key(&ctx.texture_cache, path); - textures_load(&ctx.texture_cache, path); - const SDL_Rect srcrect = textures_get_srcrect(&ctx.texture_cache, path); - - const int atlas_index = textures_get_atlas_index(&ctx.texture_cache, path); - if (atlas_index == -1) { - 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; - 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 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? */ } - struct uncolored_space_triangle *data = (struct uncolored_space_triangle *)batch_p->data; - struct uncolored_space_triangle pack = { + union uncolored_space_triangle triangle = { .primitive = { .v0 = v0, - .uv0 = uv0c, .v1 = v1, - .uv1 = uv1c, .v2 = v2, - .uv2 = uv2c, - }; - arrpush(data, pack); + .uv1 = m_to_fvec2(uv1), + .uv0 = m_to_fvec2(uv0), + .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 */ static int cmp_atlases(const void *a, const void *b) { - int index_a = ((const struct sprite_primitive *)a)->atlas_index; - int index_b = ((const struct sprite_primitive *)b)->atlas_index; + int index_a = ((const struct sprite_primitive *)a)->texture_key.id; + int index_b = ((const struct sprite_primitive *)b)->texture_key.id; 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); glClientActiveTexture(GL_TEXTURE0); - /* loner */ - if (sprite->atlas_index == -1) { - textures_bind_loner(&ctx.texture_cache, sprite->path, GL_TEXTURE_2D); - glTexCoordPointer(2, - 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, - GL_FLOAT, - 0, - /* TODO: try using shorts */ - (void *)(float[6 * 2]) { - (float)srcrect.x / TEXTURE_ATLAS_SIZE, - (float)srcrect.y / TEXTURE_ATLAS_SIZE, - (float)srcrect.x / TEXTURE_ATLAS_SIZE, - (float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE, - (float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE, - (float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE, - (float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE, - (float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE, - (float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE, - (float)srcrect.y / TEXTURE_ATLAS_SIZE, - (float)srcrect.x / TEXTURE_ATLAS_SIZE, - (float)srcrect.y / TEXTURE_ATLAS_SIZE }); - } + textures_bind(&ctx.texture_cache, sprite->texture_key, GL_TEXTURE_2D); + + t_rect srcrect = textures_get_srcrect(&ctx.texture_cache, sprite->texture_key); + t_rect dims = textures_get_dims(&ctx.texture_cache, sprite->texture_key); + + glTexCoordPointer(2, + GL_FLOAT, + 0, + /* TODO: try using shorts */ + (void *)(float[6 * 2]) { + (float)srcrect.x / (float)dims.w, + (float)srcrect.y / (float)dims.h, + (float)srcrect.x / (float)dims.w, + (float)(srcrect.y + srcrect.h) / (float)dims.h, + (float)(srcrect.x + srcrect.w) / (float)dims.w, + (float)(srcrect.y + srcrect.h) / (float)dims.h, + (float)(srcrect.x + srcrect.w) / (float)dims.w, + (float)(srcrect.y + srcrect.h) / (float)dims.h, + (float)(srcrect.x + srcrect.w) / (float)dims.w, + (float)srcrect.y / (float)dims.h, + (float)srcrect.x / (float)dims.w, + (float)srcrect.y / (float)dims.h }); 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) { - size_t data_len = arrlenu(batch->data); +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); - glBindBuffer(GL_ARRAY_BUFFER, 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; + + 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 */ glBufferData(GL_ARRAY_BUFFER, - data_len * sizeof (struct uncolored_space_triangle), - batch->data, + 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, v1), - (void *)offsetof(struct uncolored_space_triangle, v0)); + 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, v1), - (void *)offsetof(struct uncolored_space_triangle, uv0)); + offsetof(struct uncolored_space_triangle_payload, v1), + (void *)offsetof(struct uncolored_space_triangle_payload, uv0)); /* 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_VERTEX_ARRAY); @@ -498,28 +469,15 @@ static void render_space(void) { /* solid white, no modulation */ glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - for (size_t i = 0; i < arrlenu(ctx.uncolored_mesh_batches); ++i) { - if (arrlenu(&ctx.uncolored_mesh_batches[i].data) > 0) { - textures_bind_atlas(&ctx.texture_cache, (int)i, GL_TEXTURE_2D); - 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); - } + 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) { - if (ctx.texture_cache.is_dirty) - textures_update_current_atlas(&ctx.texture_cache); + textures_update_atlas(&ctx.texture_cache); /* fit rendering context onto the resizable screen */ if ((float)ctx.window_w / (float)ctx.window_h > RENDER_BASE_RATIO) { diff --git a/src/textures.c b/src/textures.c index 37cc773..1ee060d 100644 --- a/src/textures.c +++ b/src/textures.c @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -139,7 +138,12 @@ static void recreate_current_atlas_texture(struct texture_cache *cache) { SDL_BlitSurface(cache->hash[i].value.data, NULL, 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! */ @@ -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 */ 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 = (SDL_Rect) { + cache->hash[i].value.srcrect = (t_rect) { .x = rects[i].x, .y = rects[i].y, .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) { cache->window = window; sh_new_arena(cache->hash); - sh_new_arena(cache->loner_hash); 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); - 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); } @@ -282,38 +280,38 @@ void textures_dump_atlases(struct texture_cache *cache) { IMG_SavePNG_RW(cache->atlas_surfaces[i], handle, true); 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 */ - if (shgeti(cache->hash, path) >= 0 || shgeti(cache->loner_hash, path) >= 0) - return; + if (shgeti(cache->hash, path) >= 0) + return (t_texture_key){0}; SDL_Surface *surface = image_to_surface(path); - struct texture new_texture; + struct texture new_texture = {0}; new_texture.data = 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_data = new_gl_texture(); - upload_texture_from_surface(new_texture.loner_data, surface); - new_texture.atlas_index = -1; - new_texture.srcrect = (SDL_Rect) { - .w = surface->w, .h = surface->h }; - shput(cache->loner_hash, path, new_texture); + new_texture.loner_texture = new_gl_texture(); + upload_texture_from_surface(new_texture.loner_texture, surface); + new_texture.srcrect = (t_rect) { .w = surface->w, .h = surface->h }; + shput(cache->hash, path, new_texture); + return (t_texture_key){ (int)shgeti(cache->hash, path) + 1 }; } else { new_texture.atlas_index = cache->atlas_index; shput(cache->hash, path, new_texture); 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 */ stbrp_context pack_ctx; /* target info */ 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) { - int index = textures_get_atlas_index(cache, path); - if (index == -1) { - return shget(cache->loner_hash, path).srcrect; - } else if (index == INT_MIN) { +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 */ + 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){ (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.", "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 { - 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: */ -/* value of 0 - no atlas (#define NO_ATLAS (0) ?) */ -/* negative value - index in loners (-key - 1) */ -/* positive value - index in atlases (key - 1) */ -int textures_get_atlas_index(struct texture_cache *cache, const char *path) { - struct texture_cache_item *texture = shgetp_null(cache->hash, path); - - /* it might be a loner texture */ - if (texture == NULL) { - 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; - } +void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target) { + if (key.id != 0) { + if (cache->hash[key.id - 1].value.loner_texture == 0) + glBindTexture(target, cache->atlas_textures[cache->hash[key.id - 1].value.atlas_index]); + else + glBindTexture(target, cache->hash[key.id - 1].value.loner_texture); + } else if (key.id == 0) { + CRY("Texture binding failed.", + "Tried to get texture that isn't loaded."); } - - return texture->value.atlas_index; } -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) { +size_t textures_get_num_atlases(const struct texture_cache *cache) { return cache->atlas_index + 1; } diff --git a/src/textures.h b/src/textures.h index d6a5b58..b244208 100644 --- a/src/textures.h +++ b/src/textures.h @@ -2,10 +2,20 @@ #define TEXTURES_H #include "private/textures.h" +#include "util.h" #include #include +/* 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_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. */ /* 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); +// void textures_load(struct texture_cache *cache, const char *path); -/* repacks the current texture atlas based on the texture cache */ -void textures_update_current_atlas(struct texture_cache *cache); +/* 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 rect in a texture cache atlas based on a path, for drawing */ -/* if the texture is not found, returns a zero-filled rect (so check w or h) */ -SDL_Rect textures_get_srcrect(struct texture_cache *cache, const char *path); +/* 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 which atlas the texture in the path is in, starting from 0 */ -/* if the texture is not found, returns INT_MIN */ -int textures_get_atlas_index(struct texture_cache *cache, const char *path); +/* returns a rect in a texture cache of the given key */ +t_rect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key); + +/* 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 */ -void textures_bind_atlas(struct texture_cache *cache, int index, GLenum target); - -void textures_bind_loner(struct texture_cache *cache, const char *path, GLenum target); +void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target); /* 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 diff --git a/src/util.c b/src/util.c index f3d4730..9e49a64 100644 --- a/src/util.c +++ b/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) { *value = MAX(*value - 1, 0); } diff --git a/src/util.h b/src/util.h index 34c69df..03be517 100644 --- a/src/util.h +++ b/src/util.h @@ -48,7 +48,9 @@ void *ccalloc(size_t num, size_t size); #define MAX SDL_max #define MIN SDL_min +#ifndef M_PI #define M_PI 3.14159265358979323846264338327950288 /**< pi */ +#endif /* sets buf_out to a pointer to a byte buffer which must be freed. */ /* 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); +/* TODO: generics and specials (see m_to_fvec2() for an example)*/ t_frect to_frect(t_rect rect); @@ -122,6 +125,17 @@ typedef struct 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 */ /* meant for tick-based timers in game logic */ /* @@ -130,7 +144,6 @@ typedef struct shvec2 { */ 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 */