taisei/src/resource/texture_loader/texture_loader.c
Andrei Alexeyev fda8556a39
src: deprecate strdup(), use mem_strdup() explicitly
This fixes a nasty bug that manifests on windows when building without
precompiled headers. util/stringops.h used to silently replace strdup
with a macro that's compatible with mem_free(). This header would
typically be included everywhere due to PCH, but without it the strdup
from libc would sometimes be in scope. On most platforms mem_free() is
equivalent to free(), but not on windows, because we have to use
_aligned_free() there. Attempting to mem_free() the result of a libc
strdup() would segfault in such a configuration.

Avoid the footgun by banning strdup() entirely. Maybe redefining libc
names isn't such a great idea, who knew?
2024-09-10 14:31:55 +02:00

994 lines
25 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "texture_loader.h"
#include "basisu.h"
#include "util.h"
#include "util/io.h"
#include "util/kvparser.h"
void texture_loader_cleanup_stage1(TextureLoadData *ld) {
mem_free(ld->src_paths.main);
ld->src_paths.main = NULL;
mem_free(ld->src_paths.alphamap);
ld->src_paths.alphamap = NULL;
for(int i = 0; i < ARRAY_SIZE(ld->src_paths.cubemap); ++i) {
mem_free(ld->src_paths.cubemap[i]);
ld->src_paths.cubemap[i] = NULL;
}
}
void texture_loader_cleanup_stage2(TextureLoadData *ld) {
mem_free(ld->alphamap.data.untyped);
ld->alphamap.data.untyped = NULL;
if(ld->pixmaps) {
for(int i = 0; i < ld->num_pixmaps; ++i) {
mem_free(ld->pixmaps[i].data.untyped);
}
mem_free(ld->pixmaps);
ld->pixmaps = NULL;
}
}
void texture_loader_cleanup(TextureLoadData *ld) {
texture_loader_cleanup_stage1(ld);
texture_loader_cleanup_stage2(ld);
mem_free(ld);
}
void texture_loader_failed(TextureLoadData *ld) {
res_load_failed(ld->st);
texture_loader_cleanup(ld);
}
char *texture_loader_source_path(const char *basename) {
char *p = NULL;
if((p = texture_loader_basisu_try_path(basename))) {
return p;
}
return pixmap_source_path(TEX_PATH_PREFIX, basename);
}
char *texture_loader_path(const char *basename) {
char *p = NULL;
if((p = try_path(TEX_PATH_PREFIX, basename, TEX_EXTENSION))) {
return p;
}
return texture_loader_source_path(basename);
}
bool texture_loader_check_path(const char *path) {
return
strendswith(path, TEX_EXTENSION) ||
texture_loader_basisu_check_path(path) ||
pixmap_check_filename(path);
}
static bool texture_loader_parse_class(
TextureLoadData *ld,
const char *val,
TextureClass *out
) {
if(!val) {
return true;
}
char buf[strlen(val) + 1];
strcpy(buf, val);
char *ignore, *pbuf = strtok_r(buf, " \t", &ignore);
if(!SDL_strcasecmp(pbuf, "2d")) {
*out = TEXTURE_CLASS_2D;
return true;
}
if(!SDL_strcasecmp(pbuf, "cubemap")) {
*out = TEXTURE_CLASS_CUBEMAP;
return true;
}
log_error("%s: Bad class value `%s`, expected one of: 2d, cubemap", ld->st->name, pbuf);
return false;
}
static void texture_loader_parse_filter(
TextureLoadData *ld,
const char *field_name,
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_warn("%s: Bad %s value `%s`, assuming default", ld->st->name, field_name, pbuf);
}
static void texture_loader_parse_warp(
TextureLoadData *ld,
const char *field_name,
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("%s: Bad %s value `%s`, assuming default", ld->st->name, field_name, pbuf);
}
static bool texture_loader_parse_format(
TextureLoadData *ld,
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_error("%s: Invalid format '%s': Bad channels specification, expected one of: RGBA, RGB, RG, R", ld->st->name, val);
return false;
}
assert(channels <= 4);
char *end;
uint depth = strtol(val + channels, &end, 10);
if(val + channels == end) {
log_error("%s: Invalid format '%s': Bad bit depth specification, expected an integer", ld->st->name, val);
return false;
}
if(depth != 8 && depth != 16 && depth != 32) {
log_error("%s: Invalid format '%s': Invalid bit depth %d, only 8, 16, and 32 are supported", ld->st->name, 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_error("%s: Invalid format '%s': Bad data type specification, expected one of: U, UINT, F, FLOAT, or nothing", ld->st->name, val);
return false;
}
}
if(depth == 32 && !is_float) {
log_error("%s: Invalid format '%s': Bit depth %d is only supported for floating point data", ld->st->name, val, depth);
return false;
} else if(depth < 16 && is_float) {
log_error("%s: Invalid format '%s': Bit depth %d is not supported for floating point data", ld->st->name, val, depth);
return false;
}
*out = PIXMAP_MAKE_FORMAT(channels, depth) | (is_float * PIXMAP_FLOAT_BIT);
return true;
}
bool texture_loader_try_set_texture_type(
TextureLoadData *ld,
TextureType tex_type,
PixmapFormat px_fmt,
PixmapOrigin px_org,
bool srgb_fallback,
TextureTypeQueryResult *out_qr
) {
TextureTypeQueryResult qr;
bool type_supported = r_texture_type_query(tex_type, ld->params.flags, px_fmt, px_org, &qr);
if(!type_supported && (ld->params.flags & TEX_FLAG_SRGB) && srgb_fallback) {
ld->params.flags &= ~TEX_FLAG_SRGB;
ld->preprocess.linearize = true;
type_supported = r_texture_type_query(tex_type, ld->params.flags, px_fmt, px_org, &qr);
}
if(type_supported) {
ld->params.type = tex_type;
if(out_qr) {
*out_qr = qr;
}
return true;
}
return false;
}
bool texture_loader_set_texture_type_uncompressed(
TextureLoadData *ld,
TextureType tex_type,
PixmapFormat px_fmt,
PixmapOrigin px_org,
TextureTypeQueryResult *out_qr
) {
assert(!TEX_TYPE_IS_COMPRESSED(tex_type));
bool ok = texture_loader_try_set_texture_type(ld, tex_type, px_fmt, px_org, true, out_qr);
if(!ok) {
log_error("%s: Texture type %s not supported", ld->st->name, r_texture_type_name(tex_type));
return false;
}
if(ld->preprocess.linearize) {
log_warn("%s: No native sRGB support; will convert texture into linear colorspace", ld->st->name);
}
return true;
}
bool texture_loader_prepare_pixmaps(
TextureLoadData *ld,
Pixmap *pm_main,
Pixmap *pm_alphamap,
TextureType tex_type,
TextureFlags tex_flags
) {
TextureTypeQueryResult qr;
if(!r_texture_type_query(tex_type, tex_flags, pm_main->format, pm_main->origin, &qr)) {
log_error("%s: Texture type %s not supported", ld->st->name, r_texture_type_name(tex_type));
return false;
}
pixmap_convert_inplace_realloc(pm_main, qr.optimal_pixmap_format);
pixmap_flip_to_origin_inplace(pm_main, qr.optimal_pixmap_origin);
if(pm_alphamap && pm_alphamap->data.untyped) {
TextureType alphamap_type;
if(PIXMAP_FORMAT_DEPTH(pm_alphamap->format) > 8) {
alphamap_type = TEX_TYPE_R_16;
} else {
alphamap_type = TEX_TYPE_R_8;
}
if(!r_texture_type_query(alphamap_type, 0, pm_alphamap->format, pm_alphamap->origin, &qr)) {
log_error("%s: Alphamap texture type %s not supported", ld->st->name, r_texture_type_name(alphamap_type));
return false;
}
pixmap_convert_inplace_realloc(pm_alphamap, qr.optimal_pixmap_format);
pixmap_flip_to_origin_inplace(pm_alphamap, qr.optimal_pixmap_origin);
}
return true;
}
void texture_loader_continue(TextureLoadData *ld);
static bool is_preprocess_needed(TextureLoadData *ld) {
return (
ld->preprocess.linearize |
ld->preprocess.multiply_alpha |
ld->preprocess.apply_alphamap |
0
);
}
static bool texture_loader_infer_sources_2d(TextureLoadData *ld) {
ResourceLoadState *st = ld->st;
if(!ld->src_paths.main) {
ld->src_paths.main = texture_loader_source_path(st->name);
if(!ld->src_paths.main) {
log_error("%s: Couldn't infer source path from texture name", st->name);
return false;
}
log_info("%s: Inferred source path from texture name: %s", st->name, ld->src_paths.main);
}
return true;
}
static bool texture_loader_infer_sources_cubemap(TextureLoadData *ld) {
ResourceLoadState *st = ld->st;
if(ld->src_paths.main) {
if(!texture_loader_basisu_check_path(ld->src_paths.main)) {
log_error(
"%s: `source` must be a Basis Universal texture when used in a cubemap",
st->name
);
return false;
}
return true;
}
int num_defined = 0;
for(int i = 0; i < ARRAY_SIZE(ld->src_paths.cubemap); ++i) {
num_defined += ld->src_paths.cubemap[i] != NULL;
}
if(num_defined == 0) {
log_info("%s: Cubemap faces unspecified, inferring from texture name", st->name);
char buf[strlen(st->name) + 1];
memcpy(buf, st->name, sizeof(buf));
if((ld->src_paths.main = texture_loader_basisu_try_path(buf))) {
log_info("%s: Inferred Basis Universal source: %s", st->name, ld->src_paths.main);
return true;
}
} else if(num_defined < ARRAY_SIZE(ld->src_paths.cubemap)) {
log_error("%s: Incomplete cubemap faces specification", st->name);
return false;
}
typedef char suffix_t[4];
static const suffix_t suffixes[] = {
[CUBEMAP_FACE_POS_X] = "px.",
[CUBEMAP_FACE_NEG_X] = "nx.",
[CUBEMAP_FACE_POS_Y] = "py.",
[CUBEMAP_FACE_NEG_Y] = "ny.",
[CUBEMAP_FACE_POS_Z] = "pz.",
[CUBEMAP_FACE_NEG_Z] = "nz.",
};
size_t nlen = strlen(st->name);
char buf[nlen + sizeof(suffix_t) + 1];
memcpy(buf, st->name, nlen);
char *sptr = buf + nlen;
*sptr++ = '.';
for(int i = 0; i < ARRAY_SIZE(suffixes); ++i) {
log_debug("%i [%s]", i, suffixes[i]);
}
for(int i = 0; i < ARRAY_SIZE(suffixes); ++i) {
log_debug("%i [%s]", i, suffixes[i]);
memcpy(sptr, suffixes[i], sizeof(suffix_t));
ld->src_paths.cubemap[i] = pixmap_source_path(TEX_PATH_PREFIX, buf);
if(!ld->src_paths.cubemap[i]) {
log_error("%s: Failed to infer source for face %s", st->name, suffixes[i]);
return false;
}
log_info("%s: Inferred face %s source: %s", st->name, suffixes[i], ld->src_paths.cubemap[i]);
}
return true;
}
static bool load_pixmap(
TextureLoadData *ld, const char *path, Pixmap *dst, PixmapFormat preferred_format
) {
SDL_RWops *stream = res_open_file(ld->st, path, VFS_MODE_READ | VFS_MODE_SEEKABLE);
if(UNLIKELY(!stream)) {
log_error("VFS error: %s", vfs_get_error());
return false;
}
bool result = pixmap_load_stream(stream, PIXMAP_FILEFORMAT_AUTO, dst, preferred_format);
SDL_RWclose(stream);
return result;
}
static void texture_loader_cubemap_from_pixmaps(TextureLoadData *ld) {
ResourceLoadState *st = ld->st;
static_assert(sizeof(*ld->cubemaps)/sizeof(*ld->pixmaps) == ARRAY_SIZE(ld->src_paths.cubemap));
const int nsides = sizeof(*ld->cubemaps)/sizeof(*ld->pixmaps);
ld->num_pixmaps = nsides;
ld->cubemaps = ALLOC_ARRAY(1, typeof(*ld->cubemaps));
Pixmap *ref = &ld->pixmaps[0];
for(CubemapFace i = 0; i < nsides; ++i) {
const char *src = ld->src_paths.cubemap[i];
Pixmap *px = ld->pixmaps + i;
if(!load_pixmap(ld, src, ld->pixmaps + i, ld->preferred_format)) {
log_error("%s: Couldn't load cubemap face %s", st->name, src);
texture_loader_failed(ld);
return;
}
if(px->width != px->height) {
log_error("%s: %s: Cubemap face is not square (%ux%u)",
st->name, src, px->width, px->height
);
texture_loader_failed(ld);
return;
}
if(px == ref) {
ld->preferred_format = ld->preferred_format ? ld->preferred_format : px->format;
TextureType tex_type = r_texture_type_from_pixmap_format(ld->preferred_format);
bool apply_format_ok = texture_loader_set_texture_type_uncompressed(ld, tex_type, px->format, px->origin, NULL);
if(!apply_format_ok) {
texture_loader_failed(ld);
return;
}
ld->params.width = px->width;
ld->params.height = px->height;
} else {
if(px->width != ref->width || px->height != ref->height) {
log_error("%s: %s: Inconsistent cubemap face size (this: %ux%u, previous: %ux%u)",
st->name, src, px->width, px->height, ref->width, ref->height
);
texture_loader_failed(ld);
return;
}
if(px->format != ref->format) {
log_warn("%s: %s: Cubemap face pixel format differs from first face",
st->name, src
);
}
}
if(!texture_loader_prepare_pixmaps(ld, ref, NULL, ld->params.type, ld->params.flags)) {
texture_loader_failed(ld);
return;
}
}
memset(&ld->preprocess, 0, sizeof(ld->preprocess));
texture_loader_continue(ld);
}
static void texture_loader_stage2(ResourceLoadState *st);
void texture_loader_stage1(ResourceLoadState *st) {
auto ld = ALLOC(TextureLoadData, {
.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,
},
.preprocess.multiply_alpha = true,
.st = st,
});
bool want_srgb = false;
if(strendswith(st->path, TEX_EXTENSION)) {
char *str_class = NULL;
char *str_filter_min = NULL;
char *str_filter_mag = NULL;
char *str_wrap_s = NULL;
char *str_wrap_t = NULL;
char *str_format = NULL;
SDL_RWops *rw = res_open_file(st, st->path, VFS_MODE_READ);
bool parsed = parse_keyvalue_stream_with_spec(rw, (KVSpec[]) {
{ "source", .out_str = &ld->src_paths.main },
{ "alphamap", .out_str = &ld->src_paths.alphamap },
{ "cube_px", .out_str = &ld->src_paths.cubemap[CUBEMAP_FACE_POS_X] },
{ "cube_nx", .out_str = &ld->src_paths.cubemap[CUBEMAP_FACE_NEG_X] },
{ "cube_py", .out_str = &ld->src_paths.cubemap[CUBEMAP_FACE_POS_Y] },
{ "cube_ny", .out_str = &ld->src_paths.cubemap[CUBEMAP_FACE_NEG_Y] },
{ "cube_pz", .out_str = &ld->src_paths.cubemap[CUBEMAP_FACE_POS_Z] },
{ "cube_nz", .out_str = &ld->src_paths.cubemap[CUBEMAP_FACE_NEG_Z] },
{ "class", .out_str = &str_class, },
{ "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 },
{ "multiply_alpha", .out_bool = &ld->preprocess.multiply_alpha },
{ "linearize", .out_bool = &want_srgb },
{ NULL }
});
SDL_RWclose(rw);
if(UNLIKELY(!parsed)) {
texture_loader_failed(ld);
return;
}
bool class_ok = texture_loader_parse_class(ld, str_class, &ld->params.class);
mem_free(str_class);
if(!class_ok) {
texture_loader_failed(ld);
return;
}
#define BADKEY(key, var, cstr) do { \
if((var) != NULL) { \
log_warn("%s: `" key "` is not applicable for " cstr, st->name); \
mem_free(var); \
var = NULL; \
} \
} while(0)\
switch(ld->params.class) {
case TEXTURE_CLASS_2D:
BADKEY("cube_px", ld->src_paths.cubemap[CUBEMAP_FACE_POS_X], "2D textures");
BADKEY("cube_nx", ld->src_paths.cubemap[CUBEMAP_FACE_NEG_X], "2D textures");
BADKEY("cube_py", ld->src_paths.cubemap[CUBEMAP_FACE_POS_Y], "2D textures");
BADKEY("cube_ny", ld->src_paths.cubemap[CUBEMAP_FACE_NEG_Y], "2D textures");
BADKEY("cube_pz", ld->src_paths.cubemap[CUBEMAP_FACE_POS_Z], "2D textures");
BADKEY("cube_nz", ld->src_paths.cubemap[CUBEMAP_FACE_NEG_Z], "2D textures");
if(!texture_loader_infer_sources_2d(ld)) {
texture_loader_failed(ld);
return;
}
break;
case TEXTURE_CLASS_CUBEMAP:
BADKEY("alphamap", ld->src_paths.alphamap, "cubemap textures");
if(!texture_loader_infer_sources_cubemap(ld)) {
texture_loader_failed(ld);
return;
}
break;
default: UNREACHABLE;
}
texture_loader_parse_filter(ld, "filter_min", str_filter_min, &ld->params.filter.min, true);
mem_free(str_filter_min);
texture_loader_parse_filter(ld, "filter_mag", str_filter_mag, &ld->params.filter.mag, false);
mem_free(str_filter_mag);
texture_loader_parse_warp(ld, "warp_s", str_wrap_s, &ld->params.wrap.s);
mem_free(str_wrap_s);
texture_loader_parse_warp(ld, "warp_t", str_wrap_t, &ld->params.wrap.t);
mem_free(str_wrap_t);
bool format_ok = texture_loader_parse_format(ld, str_format, &ld->preferred_format);
mem_free(str_format);
if(!format_ok) {
texture_loader_failed(ld);
return;
}
} else {
ld->src_paths.main = mem_strdup(st->path);
}
if(want_srgb) {
ld->params.flags |= TEX_FLAG_SRGB;
}
assert(!ld->preprocess.linearize);
if(!ld->src_paths.main && ld->params.class == TEXTURE_CLASS_CUBEMAP) {
texture_loader_cubemap_from_pixmaps(ld);
return;
}
if(texture_loader_basisu_check_path(ld->src_paths.main)) {
mem_free(ld->src_paths.alphamap);
ld->src_paths.alphamap = NULL;
texture_loader_basisu(ld);
return;
}
ld->num_pixmaps = 1;
ld->pixmaps = ALLOC_ARRAY(1, typeof(*ld->pixmaps));
if(!load_pixmap(ld, ld->src_paths.main, ld->pixmaps, ld->preferred_format)) {
log_error("%s: Couldn't load texture image %s", st->name, ld->src_paths.main);
texture_loader_failed(ld);
return;
}
if(
ld->src_paths.alphamap &&
!load_pixmap(ld, ld->src_paths.alphamap, &ld->alphamap, PIXMAP_FORMAT_R8)
) {
log_error("%s: Couldn't load texture alphamap %s", st->name, ld->src_paths.alphamap);
texture_loader_failed(ld);
return;
}
ld->preferred_format = ld->preferred_format ? ld->preferred_format : ld->pixmaps->format;
TextureType tex_type = r_texture_type_from_pixmap_format(ld->preferred_format);
bool apply_format_ok = texture_loader_set_texture_type_uncompressed(ld, tex_type, ld->pixmaps->format, ld->pixmaps->origin, NULL);
if(!apply_format_ok) {
texture_loader_failed(ld);
return;
}
if(!texture_loader_prepare_pixmaps(ld, ld->pixmaps, &ld->alphamap, ld->params.type, ld->params.flags)) {
texture_loader_failed(ld);
return;
}
if(ld->alphamap.data.untyped) {
ld->preprocess.apply_alphamap = true;
}
if(pixmap_format_layout(ld->preferred_format) != PIXMAP_LAYOUT_RGBA) {
ld->preprocess.multiply_alpha = false;
}
ld->params.width = ld->pixmaps->width;
ld->params.height = ld->pixmaps->height;
texture_loader_continue(ld);
}
void texture_loader_continue(TextureLoadData *ld) {
texture_loader_cleanup_stage1(ld);
bool preprocess_needed = is_preprocess_needed(ld);
if(TEX_TYPE_IS_COMPRESSED(ld->params.type)) {
assert(!preprocess_needed);
if(preprocess_needed) {
log_warn("%s: Preprocessing not implemented for compressed textures", ld->st->name);
memset(&ld->preprocess, 0, sizeof(ld->preprocess));
}
}
bool have_explicit_mips;
if(ld->params.class == TEXTURE_CLASS_CUBEMAP) {
have_explicit_mips = ld->num_pixmaps > 6;
assert(ld->num_pixmaps % 6 == 0);
assert(!preprocess_needed);
if(preprocess_needed) {
log_warn("%s: Preprocessing not implemented for cubemaps", ld->st->name);
memset(&ld->preprocess, 0, sizeof(ld->preprocess));
}
} else {
have_explicit_mips = ld->num_pixmaps > 1;
}
if(have_explicit_mips) {
assert(!preprocess_needed);
if(preprocess_needed) {
log_warn("%s: Preprocessing not implemented for textures with explicit mipmaps", ld->st->name);
memset(&ld->preprocess, 0, sizeof(ld->preprocess));
}
assume(ld->alphamap.data.untyped == NULL);
assert(ld->params.mipmap_mode == TEX_MIPMAP_MANUAL);
} else {
ld->params.mipmap_mode = TEX_MIPMAP_AUTO;
}
if(ld->params.mipmaps == 0) {
ld->params.mipmaps = TEX_MIPMAPS_MAX;
}
if(ld->params.mipmaps == 1) {
// Special case: one mip level (no mipmaps).
// Disable mipmap generation and filtering modes.
log_debug("%s: Texture has only 1 mip level, adjusting filters", ld->st->name);
switch(ld->params.filter.min) {
case TEX_FILTER_LINEAR_MIPMAP_LINEAR:
case TEX_FILTER_LINEAR_MIPMAP_NEAREST:
log_debug("%s: Min filter changed to linear", ld->st->name);
ld->params.filter.min = TEX_FILTER_LINEAR;
break;
case TEX_FILTER_NEAREST_MIPMAP_LINEAR:
case TEX_FILTER_NEAREST_MIPMAP_NEAREST:
log_debug("%s: Min filter changed to nearest", ld->st->name);
ld->params.filter.min = TEX_FILTER_NEAREST;
break;
default:
break;
}
ld->params.mipmap_mode = TEX_MIPMAP_MANUAL;
}
if(is_preprocess_needed(ld)) {
res_load_dependency(ld->st, RES_SHADER_PROGRAM, "texture_post_load");
}
res_load_continue_on_main(ld->st, texture_loader_stage2, ld);
}
static Texture *texture_loader_preprocess(
TextureLoadData *ld,
Texture *tex,
Texture *alphamap
);
static void texture_loader_stage2(ResourceLoadState *st) {
TextureLoadData *ld = NOT_NULL(st->opaque);
assume(ld->st == st);
bool preprocess_needed = is_preprocess_needed(ld);
if(TEX_TYPE_IS_COMPRESSED(ld->params.type)) {
assert(!preprocess_needed);
}
assert(ld->pixmaps != NULL);
assert(ld->num_pixmaps > 0);
if(ld->num_pixmaps > 1) {
assert(!preprocess_needed);
assert(ld->alphamap.data.untyped == NULL);
}
Texture *texture;
if(preprocess_needed) {
// Create transient texture that we'll just render into another one and discard.
// Doesn't need mipmaps and filtering.
TextureParams p = ld->params;
p.mipmaps = 1;
p.mipmap_mode = TEX_MIPMAP_MANUAL;
p.filter.min = TEX_FILTER_NEAREST;
p.filter.mag = TEX_FILTER_NEAREST;
texture = r_texture_create(&p);
if(!texture) {
texture_loader_failed(ld);
return;
}
char namebuf[strlen(st->name) + sizeof(" (transient)")];
snprintf(namebuf, sizeof(namebuf), "%s (transient)", st->name);
r_texture_set_debug_label(texture, namebuf);
} else {
texture = r_texture_create(&ld->params);
if(!texture) {
texture_loader_failed(ld);
return;
}
r_texture_set_debug_label(texture, st->name);
}
if(ld->params.class == TEXTURE_CLASS_2D) {
for(uint i = 0; i < ld->num_pixmaps; ++i) {
Pixmap *p = ld->pixmaps + i;
r_texture_fill(texture, i, 0, p);
mem_free(p->data.untyped);
}
mem_free(ld->pixmaps);
ld->pixmaps = NULL;
} else if(ld->params.class == TEXTURE_CLASS_CUBEMAP) {
for(uint i = 0; i < ld->num_pixmaps / 6; ++i) {
TextureLoadCubemap *cm = ld->cubemaps + i;
#define FACE(f) \
r_texture_fill(texture, i, f, cm->faces + f); \
mem_free(cm->faces[f].data.untyped)
FACE(CUBEMAP_FACE_POS_X);
FACE(CUBEMAP_FACE_NEG_X);
FACE(CUBEMAP_FACE_POS_Y);
FACE(CUBEMAP_FACE_NEG_Y);
FACE(CUBEMAP_FACE_POS_Z);
FACE(CUBEMAP_FACE_NEG_Z);
#undef FACE
}
mem_free(ld->cubemaps);
ld->cubemaps = NULL;
} else {
UNREACHABLE;
}
Texture *alphamap = NULL;
if(ld->alphamap.data.untyped) {
assume(ld->params.class == TEXTURE_CLASS_2D);
assume(ld->preprocess.apply_alphamap);
assume(preprocess_needed);
TextureParams p = ld->params;
p.type = r_texture_type_from_pixmap_format(ld->alphamap.format);
p.mipmaps = 1;
p.mipmap_mode = TEX_MIPMAP_MANUAL;
p.filter.min = TEX_FILTER_NEAREST;
p.filter.mag = TEX_FILTER_NEAREST;
alphamap = r_texture_create(&p);
const char suffix[] = " alphamap";
char buf[strlen(st->name) + sizeof(suffix)];
snprintf(buf, sizeof(buf), "%s%s", st->name, suffix);
r_texture_set_debug_label(alphamap, buf);
r_texture_fill(alphamap, 0, 0, &ld->alphamap);
mem_free(ld->alphamap.data.untyped);
ld->alphamap.data.untyped = NULL;
}
if(preprocess_needed) {
assume(ld->params.class == TEXTURE_CLASS_2D);
texture = texture_loader_preprocess(ld, texture, alphamap);
r_texture_set_debug_label(texture, st->name);
}
texture_loader_cleanup(ld);
if(alphamap) {
r_texture_destroy(alphamap);
}
res_load_finished(st, texture);
}
static Texture *texture_loader_preprocess(
TextureLoadData *ld,
Texture *tex,
Texture *alphamap
) {
Texture *fbo_tex;
Framebuffer *fb;
r_state_push();
r_blend(BLEND_NONE);
r_disable(RCAP_CULL_FACE);
fbo_tex = r_texture_create(&ld->params);
r_shader("texture_post_load");
r_uniform_sampler("tex", tex);
r_uniform_int("linearize", ld->preprocess.linearize);
r_uniform_int("multiply_alpha", ld->preprocess.multiply_alpha);
r_uniform_int("apply_alphamap", ld->preprocess.apply_alphamap);
if(alphamap) {
r_uniform_sampler("alphamap", alphamap);
} else {
// FIXME: even though we aren't going to actually use this sampler, it
// must be set to something valid, lest WebGL throw a fit. This should be
// worked around in the renderer code, not here.
r_uniform_sampler("alphamap", tex);
}
r_mat_mv_push_identity();
r_mat_proj_push_ortho(ld->params.width, ld->params.height);
r_mat_mv_scale(ld->params.width, ld->params.height, 1);
r_mat_mv_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, ld->params.width, ld->params.height);
r_framebuffer(fb);
r_draw_quad();
r_mat_mv_pop();
r_mat_proj_pop();
r_framebuffer_destroy(fb);
r_texture_destroy(tex);
r_state_pop();
return fbo_tex;
}
void texture_loader_unload(void *vtexture) {
r_texture_destroy(vtexture);
}