From 374a9b9c58c3928e919a87bebcd8c6a5b4a6ad15 Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Thu, 1 Aug 2024 00:23:32 +0300 Subject: [PATCH] repeating textures in sprite rendering --- apps/testgame/scenes/ingame.c | 5 ++ townengine/macros/option.h | 13 +++ townengine/macros/varargcount.h | 4 +- townengine/rendering.h | 3 +- townengine/rendering/internal_api.h | 1 + townengine/rendering/sprites.h | 61 ++++++++++---- townengine/textures/internal_api.h | 4 + townengine/textures/textures.c | 124 ++++++++++++++++++---------- 8 files changed, 152 insertions(+), 63 deletions(-) diff --git a/apps/testgame/scenes/ingame.c b/apps/testgame/scenes/ingame.c index 6c69f96..1e2bd24 100644 --- a/apps/testgame/scenes/ingame.c +++ b/apps/testgame/scenes/ingame.c @@ -54,6 +54,11 @@ static void ingame_tick(struct state *state) { input_set_mouse_captured(&ctx.input, !input_is_mouse_captured(&ctx.input)); } + m_sprite(m_set(path, "/assets/light.png"), + m_set(rect, ((t_frect){ 0, 128, 256, 32 })), + m_opt(color, ((t_color){ 255, 0, 0, 255 })), + m_opt(scale, false )); + m_sprite(m_set(path, "/assets/light.png"), m_set(rect, ((t_frect){ 48, 64, 64, 64 })), m_opt(color, ((t_color){ 255, 0, 0, 255 }))); diff --git a/townengine/macros/option.h b/townengine/macros/option.h index 60238fe..86cf119 100644 --- a/townengine/macros/option.h +++ b/townengine/macros/option.h @@ -59,6 +59,19 @@ bool m2##_opt_set : 1; \ bool m3##_opt_set : 1; \ +#define m_option_list_10(t0, m0, t1, m1, t2, m2, \ + t3, m3, t4, m4) \ + t0 m0##_opt; \ + t1 m1##_opt; \ + t2 m2##_opt; \ + t3 m3##_opt; \ + t4 m4##_opt; \ + bool m0##_opt_set : 1; \ + bool m1##_opt_set : 1; \ + bool m2##_opt_set : 1; \ + bool m3##_opt_set : 1; \ + bool m4##_opt_set : 1; \ + #define m_option_list_(p_n, ...) m_concatenate(m_option_list_, p_n)(__VA_ARGS__) #define m_option_list(...) m_option_list_(m_narg(__VA_ARGS__), __VA_ARGS__) diff --git a/townengine/macros/varargcount.h b/townengine/macros/varargcount.h index cf0364a..1cbef6a 100644 --- a/townengine/macros/varargcount.h +++ b/townengine/macros/varargcount.h @@ -3,7 +3,7 @@ #define m_narg(...) m_narg_(__VA_ARGS__, m_rseq_n_()) #define m_narg_(...) m_arg_n_(__VA_ARGS__) -#define m_arg_n_(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N -#define m_rseq_n_() 8, 7, 6, 5, 4, 3, 2, 1, 0 +#define m_arg_n_(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N +#define m_rseq_n_() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 #endif diff --git a/townengine/rendering.h b/townengine/rendering.h index e56387e..b212e2c 100644 --- a/townengine/rendering.h +++ b/townengine/rendering.h @@ -17,7 +17,8 @@ typedef struct push_sprite_args { t_color, color, float, rotation, bool, flip_x, - bool, flip_y ) + bool, flip_y, + bool, scale ) } t_push_sprite_args; /* pushes a sprite onto the sprite render queue */ diff --git a/townengine/rendering/internal_api.h b/townengine/rendering/internal_api.h index 358e5b5..fed3aeb 100644 --- a/townengine/rendering/internal_api.h +++ b/townengine/rendering/internal_api.h @@ -16,6 +16,7 @@ struct sprite_primitive { t_texture_key texture_key; bool flip_x; bool flip_y; + bool repeat; }; struct rect_primitive { diff --git a/townengine/rendering/sprites.h b/townengine/rendering/sprites.h index 849422f..ca95ad8 100644 --- a/townengine/rendering/sprites.h +++ b/townengine/rendering/sprites.h @@ -69,6 +69,7 @@ void push_sprite(const t_push_sprite_args args) { .texture_key = textures_get_key(&ctx.texture_cache, args.path), .flip_x = m_or(args, flip_x, false), .flip_y = m_or(args, flip_y, false), + .repeat = !m_or(args, scale, true), }; struct primitive_2d primitive = { @@ -82,16 +83,18 @@ void push_sprite(const t_push_sprite_args args) { static struct sprite_batch { size_t size; /* how many primitives are in current batch */ - int atlas_id; enum texture_mode mode; bool constant_colored; /* whether colored batch is uniformly colored */ + bool repeat; /* whether repeat is needed */ } collect_sprite_batch(const struct primitive_2d *primitives, size_t len) { /* assumes that first primitive is already a sprite */ + const uint16_t texture_key_id = primitives[0].sprite.texture_key.id; + const int atlas_id = textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key); + struct sprite_batch batch = { - .atlas_id = - textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key), .mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key), .constant_colored = true, + .repeat = primitives[0].sprite.repeat, }; const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color; @@ -113,12 +116,22 @@ static struct sprite_batch { break; /* only collect the same texture atlases */ - if (textures_get_atlas_id(&ctx.texture_cache, current->sprite.texture_key) - != batch.atlas_id) + if (textures_get_atlas_id(&ctx.texture_cache, current->sprite.texture_key) != atlas_id) break; + /* repeated textures require separate handling */ + if (batch.repeat) { + /* all must be repeated */ + if (!current->sprite.repeat) + break; + + /* all must be of same texture id, not just atlas id */ + if (current->sprite.texture_key.id != texture_key_id) + break; + } + /* if all are modulated the same we can skip sending the color data */ - if (batch.constant_colored && *(const uint32_t *)¤t->sprite.color == uniform_color) + if (*(const uint32_t *)¤t->sprite.color != uniform_color) batch.constant_colored = false; ++batch.size; @@ -184,15 +197,30 @@ static void render_sprites(const struct primitive_2d primitives[], const t_frect srcrect = textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key); - const float wr = srcrect.w / dims.w; - const float hr = srcrect.h / dims.h; - const float xr = srcrect.x / dims.w; - const float yr = srcrect.y / dims.h; + t_fvec2 uv0, uv1, uv2, uv3; - t_fvec2 uv0 = { xr + wr * sprite.flip_x, yr + hr * sprite.flip_y }; - t_fvec2 uv1 = { xr + wr * sprite.flip_x, yr + hr * !sprite.flip_y }; - t_fvec2 uv2 = { xr + wr * !sprite.flip_x, yr + hr * !sprite.flip_y }; - t_fvec2 uv3 = { xr + wr * !sprite.flip_x, yr + hr * sprite.flip_y }; + if (!sprite.repeat) { + const float wr = srcrect.w / dims.w; + const float hr = srcrect.h / dims.h; + const float xr = srcrect.x / dims.w; + const float yr = srcrect.y / dims.h; + + uv0 = (t_fvec2){ xr + wr * sprite.flip_x, yr + hr * sprite.flip_y }; + uv1 = (t_fvec2){ xr + wr * sprite.flip_x, yr + hr * !sprite.flip_y }; + uv2 = (t_fvec2){ xr + wr * !sprite.flip_x, yr + hr * !sprite.flip_y }; + uv3 = (t_fvec2){ xr + wr * !sprite.flip_x, yr + hr * sprite.flip_y }; + + } else { + /* try fitting texture into supplied destination rectangle */ + + const float rx = sprite.rect.w / srcrect.w; + const float ry = sprite.rect.h / srcrect.h; + + uv0 = (t_fvec2){ rx * sprite.flip_x, ry * sprite.flip_y }; + uv1 = (t_fvec2){ rx * sprite.flip_x, ry * !sprite.flip_y }; + uv2 = (t_fvec2){ rx * !sprite.flip_x, ry * !sprite.flip_y }; + uv3 = (t_fvec2){ rx * !sprite.flip_x, ry * sprite.flip_y }; + } t_fvec2 v0, v1, v2, v3; @@ -309,7 +337,10 @@ static void render_sprites(const struct primitive_2d primitives[], primitives[0].sprite.color.b, primitives[0].sprite.color.a); - textures_bind(&ctx.texture_cache, primitives->sprite.texture_key, GL_TEXTURE_2D); + if (!batch.repeat) + textures_bind(&ctx.texture_cache, primitives->sprite.texture_key, GL_TEXTURE_2D); + else + textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key, GL_TEXTURE_2D); bind_quad_element_buffer(); diff --git a/townengine/textures/internal_api.h b/townengine/textures/internal_api.h index 312a3bd..b651015 100644 --- a/townengine/textures/internal_api.h +++ b/townengine/textures/internal_api.h @@ -15,6 +15,7 @@ struct texture { SDL_Surface *data; /* original image data */ int atlas_index; GLuint loner_texture; /* stored directly for loners, == 0 means atlas_index should be used */ + GLuint repeating_texture; /* separately allocated texture, for loners == loner_texture */ enum texture_mode mode; }; @@ -77,6 +78,9 @@ int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key k /* binds atlas texture in opengl state */ void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target); +/* binds texture in opengl state, ensuring that it's usable with texture repeat */ +void textures_bind_repeating(const struct texture_cache *cache, t_texture_key key, GLenum target); + /* returns helpful information about contents of alpha channel in given texture */ enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key); diff --git a/townengine/textures/textures.c b/townengine/textures/textures.c index 5c3581a..fd0a56e 100644 --- a/townengine/textures/textures.c +++ b/townengine/textures/textures.c @@ -1,6 +1,7 @@ -#include "internal_api.h" -#include "../config.h" -#include "../util.h" +#include "townengine/textures/internal_api.h" +#include "townengine/config.h" +#include "townengine/util.h" +#include "townengine/context.h" #include #include @@ -52,42 +53,9 @@ static GLuint new_gl_texture(void) { } -/* adds a new, blank atlas surface to the cache */ -static void add_new_atlas(struct texture_cache *cache) { - SDL_PixelFormat *native_format = - SDL_AllocFormat(SDL_GetWindowPixelFormat(cache->window)); - - /* the window format won't have an alpha channel, so we figure this out */ -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - uint32_t a_mask = 0x000000FF; -#else - uint32_t a_mask = 0xFF000000; -#endif - - SDL_Surface *new_atlas = SDL_CreateRGBSurface(0, - TEXTURE_ATLAS_SIZE, - TEXTURE_ATLAS_SIZE, - TEXTURE_ATLAS_BIT_DEPTH, - native_format->Rmask, - native_format->Gmask, - native_format->Bmask, - a_mask); - SDL_FreeFormat(native_format); - - SDL_SetSurfaceBlendMode(new_atlas, SDL_BLENDMODE_NONE); - SDL_SetSurfaceRLE(new_atlas, true); - arrput(cache->atlas_surfaces, new_atlas); - arrput(cache->atlas_textures, new_gl_texture()); -} - - -static void upload_texture_from_surface(GLuint texture, SDL_Surface *surface) { +static SDL_Surface *create_surface(int width, int height) { Uint32 rmask, gmask, bmask, amask; - glBindTexture(GL_TEXTURE_2D, texture); - - // glPixelStorei(GL_PACK_ALIGNMENT, 1); - #if SDL_BYTEORDER == SDL_BIG_ENDIAN rmask = 0xff000000; gmask = 0x00ff0000; @@ -100,11 +68,34 @@ static void upload_texture_from_surface(GLuint texture, SDL_Surface *surface) { amask = 0xff000000; #endif - /* TODO: don't do it if format is compatible */ - SDL_Surface* intermediate = SDL_CreateRGBSurface(0, - surface->w, surface->h, 32, rmask, gmask, bmask, amask); - SDL_BlitSurface(surface, NULL, intermediate, NULL); - SDL_LockSurface(intermediate); + SDL_Surface *surface = SDL_CreateRGBSurface(0, + width, + height, + TEXTURE_ATLAS_BIT_DEPTH, + rmask, + gmask, + bmask, + amask); + + SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE); + SDL_SetSurfaceRLE(surface, true); + + return surface; +} + + +/* adds a new, blank atlas surface to the cache */ +static void add_new_atlas(struct texture_cache *cache) { + SDL_Surface *new_atlas = create_surface(TEXTURE_ATLAS_SIZE, TEXTURE_ATLAS_SIZE); + arrput(cache->atlas_surfaces, new_atlas); + arrput(cache->atlas_textures, new_gl_texture()); +} + + +static void upload_texture_from_surface(GLuint texture, SDL_Surface *surface) { + glBindTexture(GL_TEXTURE_2D, texture); + + SDL_LockSurface(surface); glTexImage2D(GL_TEXTURE_2D, 0, @@ -114,10 +105,9 @@ static void upload_texture_from_surface(GLuint texture, SDL_Surface *surface) { 0, GL_RGBA, GL_UNSIGNED_BYTE, - intermediate->pixels); + surface->pixels); - SDL_UnlockSurface(intermediate); - SDL_FreeSurface(intermediate); + SDL_UnlockSurface(surface); glBindTexture(GL_TEXTURE_2D, 0); } @@ -400,6 +390,7 @@ void textures_update_atlas(struct texture_cache *cache) { static const char *rodata_start; static const char *rodata_stop; +/* TODO: separate and reuse */ t_texture_key textures_get_key(struct texture_cache *cache, const char *path) { static const char *last_path = NULL; static t_texture_key last_texture; @@ -501,6 +492,49 @@ void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum } +/* TODO: alternative schemes, such as: array texture, fragment shader and geometry division */ +void textures_bind_repeating(const struct texture_cache *cache, t_texture_key key, GLenum target) { + if (m_texture_key_is_valid(key)) { + if (cache->hash[key.id].value.loner_texture == 0) { + + /* already allocated */ + if (cache->hash[key.id].value.repeating_texture != 0) { + glBindTexture(target, cache->hash[key.id].value.repeating_texture); + return; + } + + const struct texture texture = cache->hash[key.id].value; + + const GLuint repeating_texture = new_gl_texture(); + glBindTexture(target, repeating_texture); + + SDL_LockSurface(texture.data); + + glTexImage2D(target, + 0, + GL_RGBA8, + texture.data->w, + texture.data->h, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + texture.data->pixels); + + SDL_UnlockSurface(texture.data); + SDL_FreeSurface(texture.data); + + cache->hash[key.id].value.repeating_texture = repeating_texture; + + } else + glBindTexture(target, cache->hash[key.id].value.loner_texture); + + } else if (key.id == 0) { + CRY("Texture binding failed.", + "Tried to get texture that isn't loaded."); + } +} + + enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key) { if (m_texture_key_is_valid(key)) { return cache->hash[key.id].value.mode;