repeating textures in sprite rendering
This commit is contained in:
parent
e4e2c203a1
commit
374a9b9c58
@ -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 })));
|
||||||
|
@ -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__)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 */
|
||||||
|
@ -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 {
|
||||||
|
@ -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 *)¤t->sprite.color == uniform_color)
|
if (*(const uint32_t *)¤t->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();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user