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?
994 lines
25 KiB
C
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);
|
|
}
|