opengl moment #1
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| #define PRIVATE_RENDERING_H | ||||
|  | ||||
| #include "../rendering.h" | ||||
| #include "../textures.h" | ||||
| #include "../util.h" | ||||
|  | ||||
| #include <SDL2/SDL.h> | ||||
| @@ -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; | ||||
| }; | ||||
|  | ||||
| /* 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 */ | ||||
| }; | ||||
| /* 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; | ||||
|  | ||||
| struct mesh_batch_item { | ||||
|     char *key; | ||||
|     struct mesh_batch value; | ||||
| }; | ||||
|  | ||||
| /* is structure that is in opengl vertex array */ | ||||
| struct uncolored_space_triangle { | ||||
|     /* 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 */ | ||||
|     size_t buffer_len;      /* element count */ | ||||
|     uint8_t *primitives; | ||||
| }; | ||||
|  | ||||
| struct mesh_batch_item { | ||||
|     t_texture_key key; | ||||
|     struct mesh_batch value; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| #ifndef PRIVATE_TEXTURES_H | ||||
| #define PRIVATE_TEXTURES_H | ||||
|  | ||||
| #include "../util.h" | ||||
|  | ||||
| #include <SDL2/SDL.h> | ||||
| #include <stb_rect_pack.h> | ||||
| #include <glad/glad.h> | ||||
| @@ -8,10 +10,10 @@ | ||||
| #include <stdbool.h> | ||||
|  | ||||
| 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,12 +29,11 @@ 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 */ | ||||
|  | ||||
|     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 */ | ||||
|   | ||||
							
								
								
									
										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_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)); | ||||
|     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? */ | ||||
|     } | ||||
|  | ||||
|         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 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); | ||||
|     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  / 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 }); | ||||
|     } | ||||
|                         (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) { | ||||
|   | ||||
							
								
								
									
										136
									
								
								src/textures.c
									
									
									
									
									
								
							
							
						
						
									
										136
									
								
								src/textures.c
									
									
									
									
									
								
							| @@ -10,7 +10,6 @@ | ||||
| #include <stb_ds.h> | ||||
| #include <stb_rect_pack.h> | ||||
|  | ||||
| #include <limits.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.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, | ||||
|                         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;     | ||||
| } | ||||
|   | ||||
| @@ -2,10 +2,20 @@ | ||||
| #define TEXTURES_H | ||||
|  | ||||
| #include "private/textures.h" | ||||
| #include "util.h" | ||||
|  | ||||
| #include <SDL2/SDL.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_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 | ||||
|   | ||||
							
								
								
									
										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) { | ||||
|     *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 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 */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user