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 */
|
||||
|
Loading…
Reference in New Issue
Block a user