repeating textures in sprite rendering

This commit is contained in:
veclav talica 2024-08-01 00:23:32 +03:00
parent e4e2c203a1
commit 374a9b9c58
8 changed files with 152 additions and 63 deletions

View File

@ -54,6 +54,11 @@ static void ingame_tick(struct state *state) {
input_set_mouse_captured(&ctx.input, !input_is_mouse_captured(&ctx.input)); 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_sprite(m_set(path, "/assets/light.png"),
m_set(rect, ((t_frect){ 48, 64, 64, 64 })), m_set(rect, ((t_frect){ 48, 64, 64, 64 })),
m_opt(color, ((t_color){ 255, 0, 0, 255 }))); m_opt(color, ((t_color){ 255, 0, 0, 255 })));

View File

@ -59,6 +59,19 @@
bool m2##_opt_set : 1; \ bool m2##_opt_set : 1; \
bool m3##_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_(p_n, ...) m_concatenate(m_option_list_, p_n)(__VA_ARGS__)
#define m_option_list(...) m_option_list_(m_narg(__VA_ARGS__), __VA_ARGS__) #define m_option_list(...) m_option_list_(m_narg(__VA_ARGS__), __VA_ARGS__)

View File

@ -3,7 +3,7 @@
#define m_narg(...) m_narg_(__VA_ARGS__, m_rseq_n_()) #define m_narg(...) m_narg_(__VA_ARGS__, m_rseq_n_())
#define m_narg_(...) m_arg_n_(__VA_ARGS__) #define m_narg_(...) m_arg_n_(__VA_ARGS__)
#define m_arg_n_(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N #define m_arg_n_(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
#define m_rseq_n_() 8, 7, 6, 5, 4, 3, 2, 1, 0 #define m_rseq_n_() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
#endif #endif

View File

@ -17,7 +17,8 @@ typedef struct push_sprite_args {
t_color, color, t_color, color,
float, rotation, float, rotation,
bool, flip_x, bool, flip_x,
bool, flip_y ) bool, flip_y,
bool, scale )
} t_push_sprite_args; } t_push_sprite_args;
/* pushes a sprite onto the sprite render queue */ /* pushes a sprite onto the sprite render queue */

View File

@ -16,6 +16,7 @@ struct sprite_primitive {
t_texture_key texture_key; t_texture_key texture_key;
bool flip_x; bool flip_x;
bool flip_y; bool flip_y;
bool repeat;
}; };
struct rect_primitive { struct rect_primitive {

View File

@ -69,6 +69,7 @@ void push_sprite(const t_push_sprite_args args) {
.texture_key = textures_get_key(&ctx.texture_cache, args.path), .texture_key = textures_get_key(&ctx.texture_cache, args.path),
.flip_x = m_or(args, flip_x, false), .flip_x = m_or(args, flip_x, false),
.flip_y = m_or(args, flip_y, false), .flip_y = m_or(args, flip_y, false),
.repeat = !m_or(args, scale, true),
}; };
struct primitive_2d primitive = { struct primitive_2d primitive = {
@ -82,16 +83,18 @@ void push_sprite(const t_push_sprite_args args) {
static struct sprite_batch { static struct sprite_batch {
size_t size; /* how many primitives are in current batch */ size_t size; /* how many primitives are in current batch */
int atlas_id;
enum texture_mode mode; enum texture_mode mode;
bool constant_colored; /* whether colored batch is uniformly colored */ 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) { } collect_sprite_batch(const struct primitive_2d *primitives, size_t len) {
/* assumes that first primitive is already a sprite */ /* 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 = { 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), .mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
.constant_colored = true, .constant_colored = true,
.repeat = primitives[0].sprite.repeat,
}; };
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color; const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
@ -113,12 +116,22 @@ static struct sprite_batch {
break; break;
/* only collect the same texture atlases */ /* only collect the same texture atlases */
if (textures_get_atlas_id(&ctx.texture_cache, current->sprite.texture_key) if (textures_get_atlas_id(&ctx.texture_cache, current->sprite.texture_key) != atlas_id)
!= batch.atlas_id)
break; 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 all are modulated the same we can skip sending the color data */
if (batch.constant_colored && *(const uint32_t *)&current->sprite.color == uniform_color) if (*(const uint32_t *)&current->sprite.color != uniform_color)
batch.constant_colored = false; batch.constant_colored = false;
++batch.size; ++batch.size;
@ -184,15 +197,30 @@ static void render_sprites(const struct primitive_2d primitives[],
const t_frect srcrect = const t_frect srcrect =
textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key); textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
const float wr = srcrect.w / dims.w; t_fvec2 uv0, uv1, uv2, uv3;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
const float yr = srcrect.y / dims.h;
t_fvec2 uv0 = { xr + wr * sprite.flip_x, yr + hr * sprite.flip_y }; if (!sprite.repeat) {
t_fvec2 uv1 = { xr + wr * sprite.flip_x, yr + hr * !sprite.flip_y }; const float wr = srcrect.w / dims.w;
t_fvec2 uv2 = { xr + wr * !sprite.flip_x, yr + hr * !sprite.flip_y }; const float hr = srcrect.h / dims.h;
t_fvec2 uv3 = { xr + wr * !sprite.flip_x, yr + hr * sprite.flip_y }; 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; 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.b,
primitives[0].sprite.color.a); 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(); bind_quad_element_buffer();

View File

@ -15,6 +15,7 @@ struct texture {
SDL_Surface *data; /* original image data */ SDL_Surface *data; /* original image data */
int atlas_index; int atlas_index;
GLuint loner_texture; /* stored directly for loners, == 0 means atlas_index should be used */ 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; 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 */ /* binds atlas texture in opengl state */
void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target); 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 */ /* 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); enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key);

View File

@ -1,6 +1,7 @@
#include "internal_api.h" #include "townengine/textures/internal_api.h"
#include "../config.h" #include "townengine/config.h"
#include "../util.h" #include "townengine/util.h"
#include "townengine/context.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_image.h> #include <SDL2/SDL_image.h>
@ -52,42 +53,9 @@ static GLuint new_gl_texture(void) {
} }
/* adds a new, blank atlas surface to the cache */ static SDL_Surface *create_surface(int width, int height) {
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) {
Uint32 rmask, gmask, bmask, amask; Uint32 rmask, gmask, bmask, amask;
glBindTexture(GL_TEXTURE_2D, texture);
// glPixelStorei(GL_PACK_ALIGNMENT, 1);
#if SDL_BYTEORDER == SDL_BIG_ENDIAN #if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0xff000000; rmask = 0xff000000;
gmask = 0x00ff0000; gmask = 0x00ff0000;
@ -100,11 +68,34 @@ static void upload_texture_from_surface(GLuint texture, SDL_Surface *surface) {
amask = 0xff000000; amask = 0xff000000;
#endif #endif
/* TODO: don't do it if format is compatible */ SDL_Surface *surface = SDL_CreateRGBSurface(0,
SDL_Surface* intermediate = SDL_CreateRGBSurface(0, width,
surface->w, surface->h, 32, rmask, gmask, bmask, amask); height,
SDL_BlitSurface(surface, NULL, intermediate, NULL); TEXTURE_ATLAS_BIT_DEPTH,
SDL_LockSurface(intermediate); 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, glTexImage2D(GL_TEXTURE_2D,
0, 0,
@ -114,10 +105,9 @@ static void upload_texture_from_surface(GLuint texture, SDL_Surface *surface) {
0, 0,
GL_RGBA, GL_RGBA,
GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE,
intermediate->pixels); surface->pixels);
SDL_UnlockSurface(intermediate); SDL_UnlockSurface(surface);
SDL_FreeSurface(intermediate);
glBindTexture(GL_TEXTURE_2D, 0); 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_start;
static const char *rodata_stop; static const char *rodata_stop;
/* TODO: separate and reuse */
t_texture_key textures_get_key(struct texture_cache *cache, const char *path) { t_texture_key textures_get_key(struct texture_cache *cache, const char *path) {
static const char *last_path = NULL; static const char *last_path = NULL;
static t_texture_key last_texture; 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) { enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key) {
if (m_texture_key_is_valid(key)) { if (m_texture_key_is_valid(key)) {
return cache->hash[key.id].value.mode; return cache->hash[key.id].value.mode;