/* * This software is licensed under the terms of the MIT License. * See COPYING for further information. * --- * Copyright (c) 2011-2019, Lukas Weber . * Copyright (c) 2012-2019, Andrei Alexeyev . */ #include "taisei.h" #include "texture.h" #include "resource.h" #include "global.h" #include "video.h" #include "renderer/api.h" #include "util/pixmap.h" static void* load_texture_begin(const char *path, uint flags); static void* load_texture_end(void *opaque, const char *path, uint flags); static void free_texture(Texture *tex); ResourceHandler texture_res_handler = { .type = RES_TEXTURE, .typename = "texture", .subdir = TEX_PATH_PREFIX, .procs = { .find = texture_path, .check = check_texture_path, .begin_load = load_texture_begin, .end_load = load_texture_end, .unload = (ResourceUnloadProc)free_texture, }, }; char* texture_path(const char *name) { char *p = NULL; if((p = try_path(TEX_PATH_PREFIX, name, TEX_EXTENSION))) { return p; } return pixmap_source_path(TEX_PATH_PREFIX, name); } bool check_texture_path(const char *path) { if(strendswith(path, TEX_EXTENSION)) { return true; } return pixmap_check_filename(path); } typedef struct TexLoadData { SDL_Surface *surf; TextureParams params; } TexLoadData; static void parse_filter(const char *val, TextureFilterMode *out, bool allow_mipmaps) { if(!val) { return; } char buf[strlen(val) + 1]; strcpy(buf, val); char *ignore, *pbuf = strtok_r(buf, " \t", &ignore); if(!SDL_strcasecmp(pbuf, "default")) { return; } if(!SDL_strcasecmp(pbuf, "nearest")) { *out = TEX_FILTER_NEAREST; return; } if(!SDL_strcasecmp(pbuf, "linear")) { *out = TEX_FILTER_LINEAR; return; } if(allow_mipmaps) { if(!SDL_strcasecmp(pbuf, "nearest_mipmap_nearest")) { *out = TEX_FILTER_NEAREST_MIPMAP_NEAREST; return; } if(!SDL_strcasecmp(pbuf, "nearest_mipmap_linear")) { *out = TEX_FILTER_NEAREST_MIPMAP_LINEAR; return; } if(!SDL_strcasecmp(pbuf, "linear_mipmap_nearest")) { *out = TEX_FILTER_LINEAR_MIPMAP_NEAREST; return; } if(!SDL_strcasecmp(pbuf, "linear_mipmap_linear")) { *out = TEX_FILTER_LINEAR_MIPMAP_LINEAR; return; } } log_fatal("Bad value `%s`, assuming default", pbuf); } static void parse_wrap(const char *val, TextureWrapMode *out) { if(!val) { return; } char buf[strlen(val) + 1]; strcpy(buf, val); char *ignore, *pbuf = strtok_r(buf, " \t", &ignore); if(!SDL_strcasecmp(pbuf, "default")) { return; } if(!SDL_strcasecmp(pbuf, "clamp")) { *out = TEX_WRAP_CLAMP; return; } if(!SDL_strcasecmp(pbuf, "mirror")) { *out = TEX_WRAP_MIRROR; return; } if(!SDL_strcasecmp(pbuf, "repeat")) { *out = TEX_WRAP_REPEAT; return; } log_warn("Bad value `%s`, assuming default", pbuf); } static TextureType pixmap_format_to_texture_type(PixmapFormat fmt) { PixmapLayout layout = PIXMAP_FORMAT_LAYOUT(fmt); uint depth = PIXMAP_FORMAT_DEPTH(fmt); bool is_float = PIXMAP_FORMAT_IS_FLOAT(fmt); if(is_float) { switch(layout) { case PIXMAP_LAYOUT_R: return TEX_TYPE_R_32_FLOAT; case PIXMAP_LAYOUT_RG: return TEX_TYPE_RG_32_FLOAT; case PIXMAP_LAYOUT_RGB: return TEX_TYPE_RGB_32_FLOAT; case PIXMAP_LAYOUT_RGBA: return TEX_TYPE_RGBA_32_FLOAT; } UNREACHABLE; } if(depth > 8) { switch(layout) { case PIXMAP_LAYOUT_R: return TEX_TYPE_R_16; case PIXMAP_LAYOUT_RG: return TEX_TYPE_RG_16; case PIXMAP_LAYOUT_RGB: return TEX_TYPE_RGB_16; case PIXMAP_LAYOUT_RGBA: return TEX_TYPE_RGBA_16; } UNREACHABLE; } switch(layout) { case PIXMAP_LAYOUT_R: return TEX_TYPE_R_8; case PIXMAP_LAYOUT_RG: return TEX_TYPE_RG_8; case PIXMAP_LAYOUT_RGB: return TEX_TYPE_RGB_8; case PIXMAP_LAYOUT_RGBA: return TEX_TYPE_RGBA_8; } UNREACHABLE; } static bool parse_format(const char *val, PixmapFormat *out) { if(!val) { return true; } uint channels = 0; char buf[strlen(val) + 1]; strcpy(buf, val); if(*val) { char *c = strrchr(buf, 0) - 1; while(isspace(*c) && c >= buf) { *c-- = 0; } } val = buf; for(uint i = 4; i >= 1; --i) { if(!SDL_strncasecmp(val, "rgba", i)) { channels = i; break; } } if(channels < 1) { log_warn("Invalid format '%s': bad channels specification, expected one of: RGBA, RGB, RG, R", val); return false; } assert(channels <= 4); char *end; uint depth = strtol(val + channels, &end, 10); if(val + channels == end) { log_warn("Invalid format '%s': bad depth specification, expected an integer", val); return false; } if(depth != 8 && depth != 16 && depth != 32) { log_warn("Invalid format '%s': invalid bit depth %d, only 8, 16, and 32 are currently supported", val, depth); return false; } while(isspace(*end)) { ++end; } bool is_float = false; if(*end) { if(!SDL_strcasecmp(end, "u") || !SDL_strcasecmp(end, "uint")) { // is_float = false; } else if(!SDL_strcasecmp(end, "f") || !SDL_strcasecmp(end, "float")) { is_float = true; } else { log_warn("Invalid format '%s': bad type specification, expected one of: U, UINT, F, FLOAT, or nothing", val); return false; } } if(depth == 32 && !is_float) { log_warn("Invalid format '%s': bit depth %d is currently only supported for floating point pixels", val, depth); return false; } else if(depth < 32 && is_float) { log_warn("Bit depth %d is currently not supported for floating point pixels, promoting to 32", depth); depth = 32; } *out = PIXMAP_MAKE_FORMAT(channels, depth) | (is_float * PIXMAP_FLOAT_BIT); return true; } typedef struct TextureLoadData { Pixmap pixmap; Pixmap pixmap_alphamap; TextureParams params; } TextureLoadData; static void* load_texture_begin(const char *path, uint flags) { const char *source = path; char *source_allocated = NULL; char *alphamap_allocated = NULL; TextureLoadData ld = { .params = { .filter = { .mag = TEX_FILTER_LINEAR, .min = TEX_FILTER_LINEAR_MIPMAP_LINEAR, }, .wrap = { .s = TEX_WRAP_REPEAT, .t = TEX_WRAP_REPEAT, }, .mipmaps = TEX_MIPMAPS_MAX, .anisotropy = TEX_ANISOTROPY_DEFAULT, // Manual because we don't want mipmaps generated until we've applied the // post-load shader, which will premultiply alpha. // Mipmaps and filtering are basically broken without premultiplied alpha. .mipmap_mode = TEX_MIPMAP_MANUAL, } }; PixmapFormat override_format = 0; if(strendswith(path, TEX_EXTENSION)) { char *str_filter_min = NULL; char *str_filter_mag = NULL; char *str_wrap_s = NULL; char *str_wrap_t = NULL; char *str_format = NULL; if(!parse_keyvalue_file_with_spec(path, (KVSpec[]) { { "source", .out_str = &source_allocated }, { "alphamap", .out_str = &alphamap_allocated }, { "filter_min", .out_str = &str_filter_min }, { "filter_mag", .out_str = &str_filter_mag }, { "wrap_s", .out_str = &str_wrap_s }, { "wrap_t", .out_str = &str_wrap_t }, { "format", .out_str = &str_format }, { "mipmaps", .out_int = (int*)&ld.params.mipmaps }, { "anisotropy", .out_int = (int*)&ld.params.anisotropy }, { NULL } })) { free(source_allocated); free(alphamap_allocated); return NULL; } if(!source_allocated) { char *basename = resource_util_basename(TEX_PATH_PREFIX, path); source_allocated = pixmap_source_path(TEX_PATH_PREFIX, basename); if(!source_allocated) { log_error("%s: couldn't infer source path from texture name", basename); } else { log_info("%s: inferred source path from texture name: %s", basename, source_allocated); } free(basename); if(!source_allocated) { return NULL; } } source = source_allocated; parse_filter(str_filter_min, &ld.params.filter.min, true); free(str_filter_min); parse_filter(str_filter_mag, &ld.params.filter.mag, false); free(str_filter_mag); parse_wrap(str_wrap_s, &ld.params.wrap.s); free(str_wrap_s); parse_wrap(str_wrap_t, &ld.params.wrap.t); free(str_wrap_t); bool format_ok = parse_format(str_format, &override_format); free(str_format); if(!format_ok) { free(source_allocated); free(alphamap_allocated); log_error("%s: bad or unsupported pixel format specification", path); return NULL; } } if(!pixmap_load_file(source, &ld.pixmap)) { log_error("%s: couldn't load texture image", source); free(source_allocated); free(alphamap_allocated); return NULL; } if(alphamap_allocated && !pixmap_load_file(alphamap_allocated, &ld.pixmap_alphamap)) { log_error("%s: couldn't load texture alphamap", alphamap_allocated); free(source_allocated); free(alphamap_allocated); return NULL; } free(source_allocated); free(alphamap_allocated); PixmapOrigin org = r_supports(RFEAT_TEXTURE_BOTTOMLEFT_ORIGIN) ? PIXMAP_ORIGIN_BOTTOMLEFT : PIXMAP_ORIGIN_TOPLEFT; pixmap_flip_to_origin_inplace(&ld.pixmap, org); if(ld.pixmap_alphamap.data.untyped) { if(PIXMAP_FORMAT_DEPTH(ld.pixmap_alphamap.format) > 8) { pixmap_convert_inplace_realloc(&ld.pixmap_alphamap, PIXMAP_FORMAT_R16); } else { pixmap_convert_inplace_realloc(&ld.pixmap_alphamap, PIXMAP_FORMAT_R8); } pixmap_flip_to_origin_inplace(&ld.pixmap_alphamap, org); } override_format = override_format ? override_format : ld.pixmap.format; ld.params.type = pixmap_format_to_texture_type(override_format); log_debug("%s: %d channels, %d bits per channel, %s", path, PIXMAP_FORMAT_LAYOUT(override_format), PIXMAP_FORMAT_DEPTH(override_format), PIXMAP_FORMAT_IS_FLOAT(override_format) ? "float" : "uint" ); if(ld.params.mipmaps == 0) { ld.params.mipmaps = TEX_MIPMAPS_MAX; } ld.params.width = ld.pixmap.width; ld.params.height = ld.pixmap.height; return memdup(&ld, sizeof(ld)); } static Texture* texture_post_load(Texture *tex, Texture *alphamap) { // this is a bit hacky and not very efficient, // but it's still much faster than fixing up the texture on the CPU ShaderProgram *shader_saved = r_shader_current(); Framebuffer *fb_saved = r_framebuffer_current(); BlendMode blend_saved = r_blend_current(); bool cullcap_saved = r_capability_current(RCAP_CULL_FACE); Texture *fbo_tex; TextureParams params; Framebuffer *fb; r_blend(BLEND_NONE); r_disable(RCAP_CULL_FACE); r_texture_get_params(tex, ¶ms); params.mipmap_mode = TEX_MIPMAP_AUTO; r_texture_set_filter(tex, TEX_FILTER_NEAREST, TEX_FILTER_NEAREST); fbo_tex = r_texture_create(¶ms); r_shader("texture_post_load"); r_uniform_sampler("tex", tex); if(alphamap) { r_uniform_sampler("alphamap", alphamap); r_uniform_int("have_alphamap", 1); } else { r_uniform_int("have_alphamap", 0); } r_mat_push(); r_mat_identity(); r_mat_mode(MM_PROJECTION); r_mat_push(); r_mat_identity(); r_mat_ortho(0, params.width, params.height, 0, -100, 100); r_mat_mode(MM_MODELVIEW); r_mat_scale(params.width, params.height, 1); r_mat_translate(0.5, 0.5, 0); fb = r_framebuffer_create(); r_framebuffer_attach(fb, fbo_tex, 0, FRAMEBUFFER_ATTACH_COLOR0); r_framebuffer_viewport(fb, 0, 0, params.width, params.height); r_framebuffer(fb); r_draw_quad(); r_mat_pop(); r_mat_mode(MM_PROJECTION); r_mat_pop(); r_mat_mode(MM_MODELVIEW); r_framebuffer(fb_saved); r_shader_ptr(shader_saved); r_blend(blend_saved); r_capability(RCAP_CULL_FACE, cullcap_saved); r_framebuffer_destroy(fb); r_texture_destroy(tex); return fbo_tex; } static void* load_texture_end(void *opaque, const char *path, uint flags) { TextureLoadData *ld = opaque; if(!ld) { return NULL; } char *basename = resource_util_basename(TEX_PATH_PREFIX, path); Texture *texture = r_texture_create(&ld->params); r_texture_set_debug_label(texture, basename); r_texture_fill(texture, 0, &ld->pixmap); free(ld->pixmap.data.untyped); Texture *alphamap = NULL; if(ld->pixmap_alphamap.data.untyped) { TextureParams p = ld->params; p.type = PIXMAP_FORMAT_DEPTH(ld->pixmap_alphamap.format) > 8 ? TEX_TYPE_R_16 : TEX_TYPE_R_8; alphamap = r_texture_create(&p); const char suffix[] = " alphamap"; char buf[strlen(basename) + sizeof(suffix)]; snprintf(buf, sizeof(buf), "%s%s", basename, suffix); r_texture_set_debug_label(alphamap, buf); r_texture_fill(alphamap, 0, &ld->pixmap_alphamap); free(ld->pixmap_alphamap.data.untyped); } free(ld); texture = texture_post_load(texture, alphamap); r_texture_set_debug_label(texture, basename); free(basename); if(alphamap) { r_texture_destroy(alphamap); } return texture; } Texture* get_tex(const char *name) { return r_texture_get(name); } Texture* prefix_get_tex(const char *name, const char *prefix) { uint plen = strlen(prefix); char buf[plen + strlen(name) + 1]; strcpy(buf, prefix); strcpy(buf + plen, name); Texture *tex = get_tex(buf); return tex; } static void free_texture(Texture *tex) { r_texture_destroy(tex); } static struct draw_texture_state { bool drawing; bool texture_matrix_tainted; } draw_texture_state; void begin_draw_texture(FloatRect dest, FloatRect frag, Texture *tex) { if(draw_texture_state.drawing) { log_fatal("Already drawing. Did you forget to call end_draw_texture, or call me on the wrong thread?"); } draw_texture_state.drawing = true; r_uniform_sampler("tex", tex); r_mat_push(); uint tw, th; r_texture_get_size(tex, 0, &tw, &th); float x = dest.x; float y = dest.y; float w = dest.w; float h = dest.h; float s = frag.w/tw; float t = frag.h/th; if(r_supports(RFEAT_TEXTURE_BOTTOMLEFT_ORIGIN)) { // FIXME: please somehow abstract this shit away! frag.y = th - frag.y - frag.h; } if(s != 1 || t != 1 || frag.x || frag.y) { draw_texture_state.texture_matrix_tainted = true; r_mat_mode(MM_TEXTURE); r_mat_identity(); r_mat_scale(1.0/tw, 1.0/th, 1); if(frag.x || frag.y) { r_mat_translate(frag.x, frag.y, 0); } if(s != 1 || t != 1) { r_mat_scale(frag.w, frag.h, 1); } r_mat_mode(MM_MODELVIEW); } if(x || y) { r_mat_translate(x, y, 0); } if(w != 1 || h != 1) { r_mat_scale(w, h, 1); } } void end_draw_texture(void) { if(!draw_texture_state.drawing) { log_fatal("Not drawing. Did you forget to call begin_draw_texture, or call me on the wrong thread?"); } if(draw_texture_state.texture_matrix_tainted) { draw_texture_state.texture_matrix_tainted = false; r_mat_mode(MM_TEXTURE); r_mat_identity(); r_mat_mode(MM_MODELVIEW); } r_mat_pop(); draw_texture_state.drawing = false; } void fill_viewport(float xoff, float yoff, float ratio, const char *name) { fill_viewport_p(xoff, yoff, ratio, 1, 0, get_tex(name)); } void fill_viewport_p(float xoff, float yoff, float ratio, float aspect, float angle, Texture *tex) { r_uniform_sampler("tex", tex); float rw, rh; if(ratio == 0) { rw = aspect; rh = 1; } else { rw = ratio * aspect; rh = ratio; } if(r_supports(RFEAT_TEXTURE_BOTTOMLEFT_ORIGIN)) { // FIXME: we should somehow account for this globally if possible... yoff *= -1; yoff += (1 - ratio); } bool texture_matrix_tainted = false; if(xoff || yoff || rw != 1 || rh != 1 || angle) { texture_matrix_tainted = true; r_mat_mode(MM_TEXTURE); if(xoff || yoff) { r_mat_translate(xoff, yoff, 0); } if(rw != 1 || rh != 1) { r_mat_scale(rw, rh, 1); } if(angle) { r_mat_translate(0.5, 0.5, 0); r_mat_rotate_deg(angle, 0, 0, 1); r_mat_translate(-0.5, -0.5, 0); } r_mat_mode(MM_MODELVIEW); } r_mat_push(); r_mat_translate(VIEWPORT_W*0.5, VIEWPORT_H*0.5, 0); r_mat_scale(VIEWPORT_W, VIEWPORT_H, 1); r_draw_quad(); r_mat_pop(); if(texture_matrix_tainted) { r_mat_mode(MM_TEXTURE); r_mat_identity(); r_mat_mode(MM_MODELVIEW); } } void fill_screen(const char *name) { fill_screen_p(get_tex(name)); } void fill_screen_p(Texture *tex) { uint tw, th; r_texture_get_size(tex, 0, &tw, &th); begin_draw_texture((FloatRect){ SCREEN_W*0.5, SCREEN_H*0.5, SCREEN_W, SCREEN_H }, (FloatRect){ 0, 0, tw, th }, tex); r_draw_quad(); end_draw_texture(); } // draws a thin, w-width rectangle from point A to point B with a texture that // moves along the line. // void loop_tex_line_p(complex a, complex b, float w, float t, Texture *texture) { complex d = b-a; complex c = (b+a)/2; r_mat_push(); r_mat_translate(creal(c),cimag(c),0); r_mat_rotate_deg(180/M_PI*carg(d),0,0,1); r_mat_scale(cabs(d),w,1); r_mat_mode(MM_TEXTURE); // r_mat_identity(); r_mat_translate(t, 0, 0); r_mat_mode(MM_MODELVIEW); r_uniform_sampler("tex", texture); r_draw_quad(); r_mat_mode(MM_TEXTURE); r_mat_identity(); r_mat_mode(MM_MODELVIEW); r_mat_pop(); } void loop_tex_line(complex a, complex b, float w, float t, const char *texture) { loop_tex_line_p(a, b, w, t, get_tex(texture)); }