renderer,texture_loader: Add support for cubemap textures

On the renderer side, the concept of a "texture class" has been
introduced. There are currently two texture classes: 2D and Cubemap.
These map to `sampler2D` and `samplerCube` in shaders, respectively.
Textures now also have an additional `layers` property. Its meaning
depends on the texture class. For simple 2D textures, there is always
only 1 layer. Cubemaps always have 6 layers, one for each face. In the
future, layers could be used to represent depth in 3D textures and
individual images in array textures.

Much of the texture loading code has been refactored, as it wasn't
adequate for loading multiple images for a single texture. Both Basis
Universal cubemaps and traditional image-based cubemaps are supported,
although no runtime preprocessing is implemented for cubemaps. The Basis
Universal format is strongly recommended.

The mkbasis utility can now convert 2:1 equirectangular panoramas into
`.basis` cubemaps with the --equirect-cubemap map.

A `vec3 fixCubeCoord(vec3 v)` function has been added to `utils.glslh`,
to convert a vector into the suitable coordinate system for sampling a
cubemap. The vector doesn't need to be normalized.
This commit is contained in:
Andrei Alexeyev 2021-06-27 11:22:39 +03:00
parent d38c33c8a2
commit 087f1ab0d9
No known key found for this signature in database
GPG key ID: 72D26128040B9690
15 changed files with 967 additions and 407 deletions

View file

@ -142,6 +142,10 @@ vec2 flip_native_to_bottomleft(vec2 v) {
return v;
}
vec3 fixCubeCoord(vec3 v) {
return v.xzy * vec3(1, -1, 1);
}
/*
* Adapted from https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl
*/

View file

@ -431,12 +431,12 @@ void r_texture_set_wrap(Texture *tex, TextureWrapMode ws, TextureWrapMode wt) {
B.texture_set_wrap(tex, ws, wt);
}
void r_texture_fill(Texture *tex, uint mipmap, const Pixmap *image_data) {
B.texture_fill(tex, mipmap, image_data);
void r_texture_fill(Texture *tex, uint mipmap, uint layer, const Pixmap *image_data) {
B.texture_fill(tex, mipmap, layer, image_data);
}
void r_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const Pixmap *image_data) {
B.texture_fill_region(tex, mipmap, x, y, image_data);
void r_texture_fill_region(Texture *tex, uint mipmap, uint layer, uint x, uint y, const Pixmap *image_data) {
B.texture_fill_region(tex, mipmap, layer, x, y, image_data);
}
void r_texture_invalidate(Texture *tex) {

View file

@ -122,6 +122,12 @@ typedef enum TextureType {
#define COMPRESSION_FORMAT_TO_TEX_TYPE(cfmt) ((TextureType)((cfmt) | TEX_TYPE_COMPRESSED_BIT))
#define TEX_TYPE_IS_DEPTH(type) ((type) >= TEX_TYPE_DEPTH_8 && (type) <= TEX_TYPE_DEPTH_32_FLOAT)
typedef enum TextureClass {
// NOTE: whichever is placed first here is considered the "default" where applicable.
TEXTURE_CLASS_2D,
TEXTURE_CLASS_CUBEMAP,
} TextureClass;
typedef enum TextureFilterMode {
// NOTE: whichever is placed first here is considered the "default" where applicable.
TEX_FILTER_LINEAR,
@ -157,10 +163,21 @@ enum {
#define TEX_MIPMAPS_MAX ((uint)(-1))
};
typedef enum CubemapFace {
CUBEMAP_FACE_POS_X,
CUBEMAP_FACE_NEG_X,
CUBEMAP_FACE_POS_Y,
CUBEMAP_FACE_NEG_Y,
CUBEMAP_FACE_POS_Z,
CUBEMAP_FACE_NEG_Z,
} CubemapFace;
typedef struct TextureParams {
uint width;
uint height;
uint layers;
TextureType type;
TextureClass class;
struct {
TextureFilterMode mag;
@ -712,8 +729,8 @@ const char* r_texture_get_debug_label(Texture *tex) attr_nonnull(1);
void r_texture_set_debug_label(Texture *tex, const char *label) attr_nonnull(1);
void r_texture_set_filter(Texture *tex, TextureFilterMode fmin, TextureFilterMode fmag) attr_nonnull(1);
void r_texture_set_wrap(Texture *tex, TextureWrapMode ws, TextureWrapMode wt) attr_nonnull(1);
void r_texture_fill(Texture *tex, uint mipmap, const Pixmap *image_data) attr_nonnull(1, 3);
void r_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const Pixmap *image_data) attr_nonnull(1, 5);
void r_texture_fill(Texture *tex, uint mipmap, uint layer, const Pixmap *image_data) attr_nonnull(1, 4);
void r_texture_fill_region(Texture *tex, uint mipmap, uint layer, uint x, uint y, const Pixmap *image_data) attr_nonnull(1, 6);
void r_texture_invalidate(Texture *tex) attr_nonnull(1);
void r_texture_clear(Texture *tex, const Color *clr) attr_nonnull(1, 2);
void r_texture_destroy(Texture *tex) attr_nonnull(1);

View file

@ -73,8 +73,8 @@ typedef struct RendererFuncs {
void (*texture_set_wrap)(Texture *tex, TextureWrapMode ws, TextureWrapMode wt);
void (*texture_destroy)(Texture *tex);
void (*texture_invalidate)(Texture *tex);
void (*texture_fill)(Texture *tex, uint mipmap, const Pixmap *image_data);
void (*texture_fill_region)(Texture *tex, uint mipmap, uint x, uint y, const Pixmap *image_data);
void (*texture_fill)(Texture *tex, uint mipmap, uint layer, const Pixmap *image_data);
void (*texture_fill_region)(Texture *tex, uint mipmap, uint layer, uint x, uint y, const Pixmap *image_data);
void (*texture_clear)(Texture *tex, const Color *clr);
bool (*texture_type_query)(TextureType type, TextureFlags flags, PixmapFormat pxfmt, PixmapOrigin pxorigin, TextureTypeQueryResult *result);

View file

@ -33,12 +33,11 @@
typedef struct TextureUnit {
LIST_INTERFACE(struct TextureUnit);
struct {
GLuint gl_handle;
Texture *active;
Texture *pending;
bool locked;
} tex2d;
Texture *active;
Texture *pending;
GLuint gl_handle;
GLenum bind_target;
bool locked;
} TextureUnit;
#define TU_INDEX(unit) ((ptrdiff_t)((unit) - R.texunits.array))
@ -299,6 +298,12 @@ static void gl33_init_context(SDL_Window *window) {
glReadBuffer(GL_BACK);
}
#ifndef STATIC_GLES3
if(glext.seamless_cubemap) {
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
}
#endif
R.viewport.active = R.viewport.default_framebuffer;
if(glext.instanced_arrays) {
@ -532,16 +537,16 @@ static void gl33_activate_texunit(TextureUnit *unit) {
}
static int gl33_texunit_priority(TextureUnit *u) {
if(u->tex2d.locked) {
assert(u->tex2d.pending);
if(u->locked) {
assert(u->pending);
return 3;
}
if(u->tex2d.pending) {
if(u->pending) {
return 2;
}
if(u->tex2d.active) {
if(u->active) {
return 1;
}
@ -566,9 +571,15 @@ attr_unused static void gl33_dump_texunits(void) {
for(TextureUnit *u = R.texunits.list.first; u; u = u->next) {
char buf1[128], buf2[128];
texture_str(u->tex2d.active, buf1, sizeof(buf1));
texture_str(u->tex2d.pending, buf2, sizeof(buf2));
log_debug("[Unit %u | %i] bound: %s; pending: %s", (uint)TU_INDEX(u), gl33_texunit_priority(u), buf1, buf2);
texture_str(u->active, buf1, sizeof(buf1));
texture_str(u->pending, buf2, sizeof(buf2));
log_debug("[Unit %u | %i | 0x%x] bound: %s; pending: %s",
(uint)TU_INDEX(u),
gl33_texunit_priority(u),
u->bind_target,
buf1,
buf2
);
}
log_debug("=== END DUMP ===");
@ -593,82 +604,100 @@ static void gl33_relocate_texuint(TextureUnit *unit) {
attr_nonnull(1)
static void gl33_set_texunit_binding(TextureUnit *unit, Texture *tex, bool lock) {
assert(!unit->tex2d.locked);
assert(!unit->locked);
if(unit->tex2d.pending == tex) {
if(unit->pending == tex) {
if(tex) {
tex->binding_unit = unit;
}
if(lock) {
unit->tex2d.locked = true;
unit->locked = true;
}
// gl33_relocate_texuint(unit);
return;
}
if(unit->tex2d.pending != NULL) {
if(unit->pending != NULL) {
// assert(unit->tex2d.pending->binding_unit == unit);
if(unit->tex2d.pending->binding_unit == unit) {
if(unit->pending->binding_unit == unit) {
// FIXME: should we search through the units for a matching binding,
// just in case another unit had the same texture bound?
unit->tex2d.pending->binding_unit = NULL;
unit->pending->binding_unit = NULL;
}
}
unit->tex2d.pending = tex;
unit->pending = tex;
if(tex) {
tex->binding_unit = unit;
}
if(lock) {
unit->tex2d.locked = true;
unit->locked = true;
}
gl33_relocate_texuint(unit);
}
static void gl33_bind_handle_to_texunit(TextureUnit *u, GLenum target, GLuint handle) {
/*
* For hysterical raisins, OpenGL texturing units can technically have multiple textures bound
* to them one for each target. However, a single texturing unit can not be accessed by two
* samplers of a distinct type from a shader, making this "feature" virtually useless.
*
* Therefore we maintain only 1 kind of texture per unit here.
*/
if(target != u->bind_target && u->bind_target != 0) {
// If changing target, clear the old binding.
// Probably not strictly necessary, but safer and more debuggable.
glBindTexture(u->bind_target, 0);
u->bind_target = target;
}
glBindTexture(target, handle);
u->gl_handle = handle;
}
void gl33_sync_texunit(TextureUnit *unit, bool prepare_rendering, bool ensure_active) {
Texture *tex = unit->tex2d.pending;
Texture *tex = unit->pending;
#ifdef GL33_DEBUG_TEXUNITS
if(unit->tex2d.pending != unit->tex2d.active) {
if(unit->pending != unit->active) {
attr_unused char buf1[128], buf2[128];
texture_str(unit->tex2d.active, buf1, sizeof(buf1));
texture_str(unit->tex2d.pending, buf2, sizeof(buf2));
texture_str(unit->active, buf1, sizeof(buf1));
texture_str(unit->pending, buf2, sizeof(buf2));
log_debug("[Unit %u] %s ===> %s", (uint)TU_INDEX(unit), buf1, buf2);
}
#endif
if(tex == NULL) {
if(unit->tex2d.gl_handle != 0) {
if(unit->gl_handle != 0) {
gl33_activate_texunit(unit);
glBindTexture(GL_TEXTURE_2D, 0);
unit->tex2d.gl_handle = 0;
unit->tex2d.active = NULL;
gl33_bind_handle_to_texunit(unit, unit->bind_target, 0);
unit->active = NULL;
gl33_relocate_texuint(unit);
}
} else if(unit->tex2d.gl_handle != tex->gl_handle) {
} else if(unit->gl_handle != tex->gl_handle) {
gl33_activate_texunit(unit);
glBindTexture(GL_TEXTURE_2D, tex->gl_handle);
unit->tex2d.gl_handle = tex->gl_handle;
gl33_bind_handle_to_texunit(unit, tex->bind_target, tex->gl_handle);
if(unit->tex2d.active == NULL) {
unit->tex2d.active = tex;
bool need_relocate = unit->active == NULL;
unit->active = tex;
if(need_relocate) {
gl33_relocate_texuint(unit);
} else {
unit->tex2d.active = tex;
}
} else if(ensure_active) {
gl33_activate_texunit(unit);
}
if(prepare_rendering && unit->tex2d.active != NULL) {
gl33_texture_prepare(unit->tex2d.active);
unit->tex2d.locked = false;
if(prepare_rendering && unit->active != NULL) {
gl33_texture_prepare(unit->active);
unit->locked = false;
}
}
@ -813,8 +842,8 @@ uint gl33_bind_texture(Texture *texture, bool for_rendering, int preferred_unit)
assert(preferred_unit < R.texunits.limit);
TextureUnit *u = &R.texunits.array[preferred_unit];
if(u->tex2d.pending == texture) {
u->tex2d.locked |= for_rendering;
if(u->pending == texture) {
u->locked |= for_rendering;
} else {
gl33_set_texunit_binding(u, texture, for_rendering);
}
@ -827,19 +856,19 @@ uint gl33_bind_texture(Texture *texture, bool for_rendering, int preferred_unit)
// assert(R.texunits.list.first->tex2d.pending != texture);
if(
R.texunits.list.first->tex2d.pending &&
R.texunits.list.first->tex2d.pending != R.texunits.list.first->tex2d.active
R.texunits.list.first->pending &&
R.texunits.list.first->pending != R.texunits.list.first->active
) {
log_warn("Ran out of texturing units, expect rendering errors!");
}
gl33_set_texunit_binding(R.texunits.list.first, texture, for_rendering);
} else /* if(for_rendering) */ {
texture->binding_unit->tex2d.locked |= for_rendering;
texture->binding_unit->locked |= for_rendering;
gl33_relocate_texuint(texture->binding_unit);
}
assert(texture->binding_unit->tex2d.pending == texture);
assert(texture->binding_unit->pending == texture);
return TU_INDEX(texture->binding_unit);
}
@ -866,19 +895,19 @@ void gl33_texture_deleted(Texture *tex) {
for(TextureUnit *unit = R.texunits.array; unit < R.texunits.array + R.texunits.limit; ++unit) {
bool bump = false;
if(unit->tex2d.pending == tex) {
unit->tex2d.pending = NULL;
unit->tex2d.locked = false;
if(unit->pending == tex) {
unit->pending = NULL;
unit->locked = false;
bump = true;
}
if(unit->tex2d.active == tex) {
assert(unit->tex2d.gl_handle == tex->gl_handle);
unit->tex2d.active = NULL;
unit->tex2d.gl_handle = 0;
if(unit->active == tex) {
assert(unit->gl_handle == tex->gl_handle);
unit->active = NULL;
unit->gl_handle = 0;
bump = true;
} else {
assert(unit->tex2d.gl_handle != tex->gl_handle);
assert(unit->gl_handle != tex->gl_handle);
}
if(bump) {

View file

@ -311,17 +311,18 @@ static bool cache_uniforms(ShaderProgram *prog) {
}
switch(type) {
case GL_FLOAT: uni.type = UNIFORM_FLOAT; break;
case GL_FLOAT_VEC2: uni.type = UNIFORM_VEC2; break;
case GL_FLOAT_VEC3: uni.type = UNIFORM_VEC3; break;
case GL_FLOAT_VEC4: uni.type = UNIFORM_VEC4; break;
case GL_INT: uni.type = UNIFORM_INT; break;
case GL_INT_VEC2: uni.type = UNIFORM_IVEC2; break;
case GL_INT_VEC3: uni.type = UNIFORM_IVEC3; break;
case GL_INT_VEC4: uni.type = UNIFORM_IVEC4; break;
case GL_SAMPLER_2D: uni.type = UNIFORM_SAMPLER; break;
case GL_FLOAT_MAT3: uni.type = UNIFORM_MAT3; break;
case GL_FLOAT_MAT4: uni.type = UNIFORM_MAT4; break;
case GL_FLOAT: uni.type = UNIFORM_FLOAT; break;
case GL_FLOAT_VEC2: uni.type = UNIFORM_VEC2; break;
case GL_FLOAT_VEC3: uni.type = UNIFORM_VEC3; break;
case GL_FLOAT_VEC4: uni.type = UNIFORM_VEC4; break;
case GL_INT: uni.type = UNIFORM_INT; break;
case GL_INT_VEC2: uni.type = UNIFORM_IVEC2; break;
case GL_INT_VEC3: uni.type = UNIFORM_IVEC3; break;
case GL_INT_VEC4: uni.type = UNIFORM_IVEC4; break;
case GL_SAMPLER_2D: uni.type = UNIFORM_SAMPLER; break;
case GL_SAMPLER_CUBE: uni.type = UNIFORM_SAMPLER; break;
case GL_FLOAT_MAT3: uni.type = UNIFORM_MAT3; break;
case GL_FLOAT_MAT4: uni.type = UNIFORM_MAT4; break;
default:
log_warn("Uniform '%s' is of an unsupported type 0x%04x and will be ignored.", name, type);

View file

@ -14,6 +14,17 @@
#include "gl33.h"
#include "../glcommon/debug.h"
static GLenum class_to_gltarget(TextureClass cls) {
switch(cls) {
case TEXTURE_CLASS_2D:
return GL_TEXTURE_2D;
case TEXTURE_CLASS_CUBEMAP:
return GL_TEXTURE_CUBE_MAP;
default:
UNREACHABLE;
}
}
static GLenum linear_to_nearest(GLenum filter) {
switch(filter) {
case GL_LINEAR:
@ -160,7 +171,32 @@ void gl33_texture_get_size(Texture *tex, uint mipmap, uint *width, uint *height)
}
}
static void gl33_texture_set(Texture *tex, uint mipmap, const Pixmap *image) {
static GLenum target_from_class_and_layer(TextureClass cls, uint layer) {
GLenum target = class_to_gltarget(cls);
if(target == GL_TEXTURE_CUBE_MAP) {
static GLenum facemap[] = {
[CUBEMAP_FACE_POS_X] = GL_TEXTURE_CUBE_MAP_POSITIVE_X,
[CUBEMAP_FACE_NEG_X] = GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
// NOTE: apaprently these are swapped in OpenGL…
[CUBEMAP_FACE_POS_Y] = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
[CUBEMAP_FACE_NEG_Y] = GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
[CUBEMAP_FACE_POS_Z] = GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
[CUBEMAP_FACE_NEG_Z] = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
static_assert_nomsg(ARRAY_SIZE(facemap) == 6);
assert(layer < ARRAY_SIZE(facemap));
return facemap[layer];
} else if(target == GL_TEXTURE_2D) {
assert(layer == 0);
}
return target;
}
static void gl33_texture_set(Texture *tex, uint mipmap, uint layer, const Pixmap *image) {
assert(mipmap < tex->params.mipmaps);
assert(image != NULL);
@ -191,11 +227,14 @@ static void gl33_texture_set(Texture *tex, uint mipmap, const Pixmap *image) {
assert(width == image->width);
assert(height == image->height);
GLenum gl_target = target_from_class_and_layer(tex->params.class, layer);
GLenum ifmt = tex->fmt_info->internal_format;
if(tex->fmt_info->flags & GLTEX_COMPRESSED) {
glCompressedTexImage2D(
GL_TEXTURE_2D,
gl_target,
mipmap,
tex->fmt_info->internal_format,
ifmt,
width,
height,
0,
@ -203,15 +242,17 @@ static void gl33_texture_set(Texture *tex, uint mipmap, const Pixmap *image) {
image_data
);
} else {
GLenum xfmt = xfer->gl_format;
GLenum xtype = xfer->gl_type;
glTexImage2D(
GL_TEXTURE_2D,
gl_target,
mipmap,
tex->fmt_info->internal_format,
width,
height,
0,
xfer->gl_format,
xfer->gl_type,
xfmt,
xtype,
image_data
);
}
@ -223,7 +264,7 @@ static void gl33_texture_set(Texture *tex, uint mipmap, const Pixmap *image) {
tex->mipmaps_outdated = true;
}
static void apply_swizzle(GLenum param, char val) {
static void apply_swizzle(GLenum gl_target, GLenum param, char val) {
GLenum swizzle_val, default_val;
switch(param) {
@ -247,24 +288,49 @@ static void apply_swizzle(GLenum param, char val) {
if(glext.texture_swizzle) {
assert(r_supports(RFEAT_TEXTURE_SWIZZLE));
glTexParameteri(GL_TEXTURE_2D, param, swizzle_val);
glTexParameteri(gl_target, param, swizzle_val);
} else if(default_val != swizzle_val) {
log_warn("Texture swizzle mask differs from default; not supported in this OpenGL version!");
}
}
static void apply_swizzle_mask(SwizzleMask *mask) {
apply_swizzle(GL_TEXTURE_SWIZZLE_R, mask->r);
apply_swizzle(GL_TEXTURE_SWIZZLE_G, mask->g);
apply_swizzle(GL_TEXTURE_SWIZZLE_B, mask->b);
apply_swizzle(GL_TEXTURE_SWIZZLE_A, mask->a);
static void apply_swizzle_mask(GLenum gl_target, SwizzleMask *mask) {
apply_swizzle(gl_target, GL_TEXTURE_SWIZZLE_R, mask->r);
apply_swizzle(gl_target, GL_TEXTURE_SWIZZLE_G, mask->g);
apply_swizzle(gl_target, GL_TEXTURE_SWIZZLE_B, mask->b);
apply_swizzle(gl_target, GL_TEXTURE_SWIZZLE_A, mask->a);
}
Texture* gl33_texture_create(const TextureParams *params) {
Texture *gl33_texture_create(const TextureParams *params) {
Texture *tex = calloc(1, sizeof(Texture));
memcpy(&tex->params, params, sizeof(*params));
TextureParams *p = &tex->params;
TextureClass cls = p->class;
GLenum gl_target = class_to_gltarget(p->class);
tex->bind_target = gl_target;
uint required_layers;
switch(cls) {
case TEXTURE_CLASS_2D: required_layers = 1; break;
case TEXTURE_CLASS_CUBEMAP: required_layers = 6; break;
default: UNREACHABLE;
}
if(p->layers == 0) {
p->layers = required_layers;
}
assert(p->layers == required_layers);
assert(p->width > 0);
assert(p->height > 0);
if(cls == TEXTURE_CLASS_CUBEMAP) {
assert(p->width == p->height);
}
uint max_mipmaps = r_texture_util_max_num_miplevels(p->width, p->height);
assert(max_mipmaps > 0);
@ -306,43 +372,44 @@ Texture* gl33_texture_create(const TextureParams *params) {
GLTextureTransferFormatInfo *xfer = &tex->fmt_info->transfer_format;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, r_wrap_to_gl_wrap(p->wrap.s));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, r_wrap_to_gl_wrap(p->wrap.t));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, r_filter_to_gl_filter(p->filter.min, tex->fmt_info));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, r_filter_to_gl_filter(p->filter.mag, tex->fmt_info));
glTexParameteri(gl_target, GL_TEXTURE_WRAP_S, r_wrap_to_gl_wrap(p->wrap.s));
glTexParameteri(gl_target, GL_TEXTURE_WRAP_T, r_wrap_to_gl_wrap(p->wrap.t));
glTexParameteri(gl_target, GL_TEXTURE_MIN_FILTER, r_filter_to_gl_filter(p->filter.min, tex->fmt_info));
glTexParameteri(gl_target, GL_TEXTURE_MAG_FILTER, r_filter_to_gl_filter(p->filter.mag, tex->fmt_info));
apply_swizzle_mask(&tex->params.swizzle);
apply_swizzle_mask(gl_target, &tex->params.swizzle);
if(partial_mipmaps_ok) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p->mipmaps - 1);
glTexParameteri(gl_target, GL_TEXTURE_MAX_LEVEL, p->mipmaps - 1);
}
if(glext.texture_filter_anisotropic) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, p->anisotropy);
glTexParameteri(gl_target, GL_TEXTURE_MAX_ANISOTROPY, p->anisotropy);
}
if((p->flags & TEX_FLAG_STREAM) && glext.pixel_buffer_object) {
glGenBuffers(1, &tex->pbo);
}
GLenum ifmt = tex->fmt_info->internal_format;
GLenum xfmt = xfer->gl_format;
GLenum xtype = xfer->gl_type;
for(uint i = 0; i < p->mipmaps; ++i) {
uint width, height;
gl33_texture_get_size(tex, i, &width, &height);
uint w, h;
gl33_texture_get_size(tex, i, &w, &h);
if(tex->fmt_info->flags & GLTEX_COMPRESSED) {
// XXX: can't pre-allocate this without ARB_texture_storage or equivalent
} else if(cls == TEXTURE_CLASS_CUBEMAP) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, i, ifmt, w, h, 0, xfmt, xtype, NULL);
} else {
glTexImage2D(
GL_TEXTURE_2D,
i,
tex->fmt_info->internal_format,
width,
height,
0,
xfer->gl_format,
xfer->gl_type,
NULL
);
glTexImage2D(gl_target, i, ifmt, w, h, 0, xfmt, xtype, NULL);
}
}
@ -358,14 +425,14 @@ void gl33_texture_set_filter(Texture *tex, TextureFilterMode fmin, TextureFilter
gl33_bind_texture(tex, false, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->params.filter.min = fmin;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, r_filter_to_gl_filter(fmin, tex->fmt_info));
glTexParameteri(tex->bind_target, GL_TEXTURE_MIN_FILTER, r_filter_to_gl_filter(fmin, tex->fmt_info));
}
if(tex->params.filter.mag != fmag) {
gl33_bind_texture(tex, false, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->params.filter.mag = fmag;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, r_filter_to_gl_filter(fmag, tex->fmt_info));
glTexParameteri(tex->bind_target, GL_TEXTURE_MAG_FILTER, r_filter_to_gl_filter(fmag, tex->fmt_info));
}
}
@ -374,14 +441,14 @@ void gl33_texture_set_wrap(Texture *tex, TextureWrapMode ws, TextureWrapMode wt)
gl33_bind_texture(tex, false, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->params.wrap.s = ws;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, r_wrap_to_gl_wrap(ws));
glTexParameteri(tex->bind_target, GL_TEXTURE_WRAP_S, r_wrap_to_gl_wrap(ws));
}
if(tex->params.wrap.t != wt) {
gl33_bind_texture(tex, false, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->params.wrap.t = wt;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, r_wrap_to_gl_wrap(wt));
glTexParameteri(tex->bind_target, GL_TEXTURE_WRAP_T, r_wrap_to_gl_wrap(wt));
}
}
@ -394,30 +461,33 @@ void gl33_texture_invalidate(Texture *tex) {
gl33_bind_texture(tex, false, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
for(uint i = 0; i < tex->params.mipmaps; ++i) {
uint width, height;
gl33_texture_get_size(tex, i, &width, &height);
GLenum ifmt = tex->fmt_info->internal_format;
GLenum xfmt = tex->fmt_info->transfer_format.gl_format;
GLenum xtype = tex->fmt_info->transfer_format.gl_type;
glTexImage2D(
GL_TEXTURE_2D,
i,
tex->fmt_info->internal_format,
width,
height,
0,
tex->fmt_info->transfer_format.gl_format,
tex->fmt_info->transfer_format.gl_type,
NULL
);
for(uint i = 0; i < tex->params.mipmaps; ++i) {
uint w, h;
gl33_texture_get_size(tex, i, &w, &h );
if(tex->params.class == TEXTURE_CLASS_CUBEMAP) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, i, ifmt, w, h, 0, xfmt, xtype, NULL);
} else {
glTexImage2D(tex->bind_target, i, ifmt, w, h, 0, xfmt, xtype, NULL);
}
}
}
void gl33_texture_fill(Texture *tex, uint mipmap, const Pixmap *image) {
void gl33_texture_fill(Texture *tex, uint mipmap, uint layer, const Pixmap *image) {
assert(mipmap == 0 || tex->params.mipmap_mode != TEX_MIPMAP_AUTO);
gl33_texture_set(tex, mipmap, image);
gl33_texture_set(tex, mipmap, layer, image);
}
void gl33_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const Pixmap *image) {
void gl33_texture_fill_region(Texture *tex, uint mipmap, uint layer, uint x, uint y, const Pixmap *image) {
assert(mipmap == 0 || tex->params.mipmap_mode != TEX_MIPMAP_AUTO);
gl33_bind_texture(tex, false, -1);
@ -429,9 +499,11 @@ void gl33_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const P
assert(qr.supplied_pixmap_origin_supported);
GLTextureTransferFormatInfo *xfer = &tex->fmt_info->transfer_format;
GLenum gl_target = target_from_class_and_layer(tex->params.class, layer);
if(tex->fmt_info->flags & GLTEX_COMPRESSED) {
glCompressedTexSubImage2D(
GL_TEXTURE_2D, mipmap,
gl_target, mipmap,
x, tex->params.height - y - image->height, image->width, image->height,
tex->fmt_info->internal_format,
image->data_size,
@ -439,7 +511,7 @@ void gl33_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const P
);
} else {
glTexSubImage2D(
GL_TEXTURE_2D, mipmap,
gl_target, mipmap,
x, tex->params.height - y - image->height, image->width, image->height,
xfer->gl_format,
xfer->gl_type,
@ -454,6 +526,7 @@ void gl44_texture_clear(Texture *tex, const Color *clr) {
#ifdef STATIC_GLES3
UNREACHABLE;
#else
assert(tex->params.class == TEXTURE_CLASS_2D);
for(int i = 0; i < tex->params.mipmaps; ++i) {
glClearTexImage(tex->gl_handle, i, GL_RGBA, GL_FLOAT, &clr->r);
}
@ -461,6 +534,7 @@ void gl44_texture_clear(Texture *tex, const Color *clr) {
}
void gl33_texture_clear(Texture *tex, const Color *clr) {
assert(tex->params.class == TEXTURE_CLASS_2D);
// TODO: maybe find a more efficient method
Framebuffer *temp_fb = r_framebuffer_create();
r_framebuffer_attach(temp_fb, tex, 0, FRAMEBUFFER_ATTACH_COLOR0);
@ -490,12 +564,12 @@ void gl33_texture_prepare(Texture *tex) {
gl33_bind_texture(tex, false, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
glGenerateMipmap(GL_TEXTURE_2D);
glGenerateMipmap(tex->bind_target);
tex->mipmaps_outdated = false;
}
}
const char* gl33_texture_get_debug_label(Texture *tex) {
const char *gl33_texture_get_debug_label(Texture *tex) {
return tex->debug_label;
}

View file

@ -22,6 +22,7 @@ typedef struct Texture {
TextureUnit *binding_unit;
GLuint gl_handle;
GLuint pbo;
GLenum bind_target;
TextureParams params;
bool mipmaps_outdated;
char debug_label[R_DEBUG_LABEL_SIZE];
@ -35,8 +36,8 @@ void gl33_texture_set_debug_label(Texture *tex, const char *label);
void gl33_texture_set_filter(Texture *tex, TextureFilterMode fmin, TextureFilterMode fmag);
void gl33_texture_set_wrap(Texture *tex, TextureWrapMode ws, TextureWrapMode wt);
void gl33_texture_invalidate(Texture *tex);
void gl33_texture_fill(Texture *tex, uint mipmap, const Pixmap *image);
void gl33_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const Pixmap *image);
void gl33_texture_fill(Texture *tex, uint mipmap, uint layer, const Pixmap *image);
void gl33_texture_fill_region(Texture *tex, uint mipmap, uint layer, uint x, uint y, const Pixmap *image);
void gl33_texture_prepare(Texture *tex);
void gl33_texture_taint(Texture *tex);
void gl44_texture_clear(Texture *tex, const Color *clr);

View file

@ -226,6 +226,20 @@ static void glcommon_ext_pixel_buffer_object(void) {
#endif
}
static void glcommon_ext_seamless_cubemap(void) {
EXT_FLAG(seamless_cubemap);
#ifndef STATIC_GLES3
// NOTE: GLES3 seems to provide seamless sampling by default,
// but it must be glEnabled in plain OpenGL, which is what this extension is about.
CHECK_CORE(GL_ATLEAST(3, 2));
CHECK_EXT(GL_ARB_seamless_cube_map);
#endif
EXT_MISSING();
}
static void glcommon_ext_depth_texture(void) {
EXT_FLAG(depth_texture);
@ -898,6 +912,7 @@ void glcommon_check_capabilities(void) {
glcommon_ext_instanced_arrays();
glcommon_ext_internalformat_query2();
glcommon_ext_pixel_buffer_object();
glcommon_ext_seamless_cubemap();
glcommon_ext_texture_filter_anisotropic();
glcommon_ext_texture_float();
glcommon_ext_texture_float_linear();

View file

@ -178,6 +178,7 @@ struct glext_s {
ext_flag_t instanced_arrays;
ext_flag_t internalformat_query2;
ext_flag_t pixel_buffer_object;
ext_flag_t seamless_cubemap;
ext_flag_t texture_filter_anisotropic;
ext_flag_t texture_float;
ext_flag_t texture_float_linear;

View file

@ -85,8 +85,8 @@ static void null_texture_set_debug_label(Texture *tex, const char *label) { }
static const char* null_texture_get_debug_label(Texture *tex) { return "null texture"; }
static void null_texture_set_filter(Texture *tex, TextureFilterMode fmin, TextureFilterMode fmag) { }
static void null_texture_set_wrap(Texture *tex, TextureWrapMode fmin, TextureWrapMode fmag) { }
static void null_texture_fill(Texture *tex, uint mipmap, const Pixmap *image_data) { }
static void null_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const Pixmap *image_data) { }
static void null_texture_fill(Texture *tex, uint mipmap, uint layer, const Pixmap *image_data) { }
static void null_texture_fill_region(Texture *tex, uint mipmap, uint layer, uint x, uint y, const Pixmap *image_data) { }
static void null_texture_invalidate(Texture *tex) { }
static void null_texture_destroy(Texture *tex) { }
static void null_texture_clear(Texture *tex, const Color *color) { }

View file

@ -406,7 +406,7 @@ static bool add_glyph_to_spritesheet(Glyph *glyph, Pixmap *pixmap, SpriteSheet *
r_texture_fill_region(
ss->tex,
0,
0, 0,
rect_x(sprite_pos),
rect_y(sprite_pos),
pixmap

View file

@ -132,7 +132,7 @@ static struct basis_size_info texture_loader_basisu_get_transcoded_size_info(
if(!basist_is_format_supported(format, src_format)) {
log_error("%s: Can't transcode from %s into %s",
ld->tex_src_path,
ld->src_paths.main,
get_basis_source_format_name(src_format),
basist_get_format_name(format)
);
@ -372,6 +372,13 @@ uncompressed:
struct basisu_load_data {
char *filebuf;
basist_transcoder *tc;
uint mip_bias;
PixmapFormat px_decode_format;
PixmapOrigin px_origin;
bool transcoding_started;
bool swizzle_supported;
bool is_uncompressed_fallback;
char basis_hash[BASISU_HASH_SIZE];
};
static void texture_loader_basisu_cleanup(struct basisu_load_data *bld) {
@ -467,42 +474,294 @@ static void texture_loader_basisu_set_swizzle(TextureLoadData *ld, PixmapFormat
}
}
#define TRY(func, ...) \
if(UNLIKELY(!(func(__VA_ARGS__)))) { \
log_error("%s: " #func "() failed", ctx); \
texture_loader_basisu_failed(ld, &bld); \
return; \
}
#define TRY_SILENT(func, ...) \
if(UNLIKELY(!(func(__VA_ARGS__)))) { \
texture_loader_basisu_failed(ld, &bld); \
return; \
}
#define TRY_BOOL(func, ...) \
if(UNLIKELY(!(func(__VA_ARGS__)))) { \
log_error("%s: " #func "() failed", ctx); \
return false; \
}
static bool texture_loader_basisu_check_consistency(
const char *ctx,
basist_transcoder *tc,
basist_file_info *file_info,
basist_image_info img_infos[]
) {
basist_image_info *ref = img_infos;
if(ref->total_levels < 1) {
log_error("%s: No levels in image 0 in Basis Universal texture", ctx);
return false;
}
bool ok = true;
for(uint i = 1; i < file_info->total_images; ++i) {
basist_image_info *img = img_infos + i;
if(img->width != ref->width || img->height != ref->height) {
log_error(
"%s: Image %i size is different from image 0 (%ux%u != %ux%u); "
"this is not allowed",
ctx, i, img->width, img->height, ref->width, ref->height
);
ok = false;
}
if(img->total_levels != ref->total_levels) {
log_error(
"%s: Image %i has different number of mip levels from image 0 (%u != %u); "
"this is not allowed",
ctx, i, img->total_levels, ref->total_levels
);
ok = false;
}
// TODO: check if level dimensions are sane?
}
return ok;
}
static bool texture_loader_basisu_sanitize_levels(
const char *ctx,
basist_transcoder *tc,
const basist_image_info *image_info,
uint *out_total_levels
) {
/*
* NOTE: We assume the renderer wants all mip levels of compressed textures to have
* dimensions divisible by 4 (if dimension > 2). This is true for most (all?) BC[1-7]
* implementations in GLES, but other formats and/or renderers may have more lax requirements
* (core GL with ARB_texture_compression_{s3tc,bptc} in particular), or even stricter
* requirements.
*
* TODO: Add a renderer API to query such requirements on a per-format basis, and use it
* here.
*/
for(uint i = 1; i < image_info->total_levels; ++i) {
basist_image_level_desc level_desc;
TRY_BOOL(
basist_transcoder_get_image_level_desc, tc, image_info->image_index, i, &level_desc
);
if(
(level_desc.orig_width > 2 && level_desc.orig_width % 4) ||
(level_desc.orig_height > 2 && level_desc.orig_height % 4)
) {
log_warn(
"%s: Mip level %i dimensions are not multiples of 4 (%ix%i); "
"number of levels reduced %i -> %i",
ctx, i, level_desc.orig_width, level_desc.orig_height, image_info->total_levels, i
);
*out_total_levels = i;
return true;
}
}
*out_total_levels = image_info->total_levels;
return true;
}
static bool texture_loader_basisu_init_mipmaps(
const char *ctx,
struct basisu_load_data *bld,
TextureLoadData *ld,
basist_image_info *ref_img
) {
uint total_levels;
if(bld->is_uncompressed_fallback) {
total_levels = ref_img->total_levels;
} else {
TRY_BOOL(texture_loader_basisu_sanitize_levels, ctx, bld->tc, ref_img, &total_levels);
}
int mip_bias = env_get("TAISEI_BASISU_MIP_BIAS", 0);
mip_bias = iclamp(mip_bias, 0, total_levels - 1);
{
int max_levels = env_get("TAISEI_BASISU_MAX_MIP_LEVELS", 0);
if(max_levels > 0) {
total_levels = iclamp(total_levels, 1, mip_bias + max_levels);
}
}
bld->mip_bias = mip_bias;
ld->params.mipmaps = total_levels - mip_bias;
ld->params.mipmap_mode = TEX_MIPMAP_MANUAL;
basist_image_level_desc zero_level;
TRY_BOOL(
basist_transcoder_get_image_level_desc,
bld->tc, ref_img->image_index, mip_bias, &zero_level
);
uint max_mips = r_texture_util_max_num_miplevels(zero_level.orig_width, zero_level.orig_height);
if(
ld->params.mipmaps > 1 &&
ld->params.mipmaps < max_mips &&
!r_supports(RFEAT_PARTIAL_MIPMAPS)
) {
log_warn(
"%s: Texture has partial mipmaps (%i levels out of %i), "
"but the renderer doesn't support this. "
"Mipmapping will be disabled for this texture",
ctx, ld->params.mipmaps, max_mips
);
ld->params.mipmaps = 1;
// TODO: In case of decompression fallback,
// we could switch to auto-generated mipmaps here instead.
// Untested code follows:
#if 0
if(bld->is_uncompressed_fallback) {
ld->params.mipmaps = max_mips;
ld->params.mipmap_mode = TEX_MIPMAP_AUTO;
}
#endif
}
switch(ld->params.class) {
case TEXTURE_CLASS_2D:
ld->pixmaps = calloc(ld->params.mipmaps, sizeof(*ld->pixmaps));
ld->num_pixmaps = ld->params.mipmaps;
break;
case TEXTURE_CLASS_CUBEMAP:
ld->cubemaps = calloc(ld->params.mipmaps, sizeof(*ld->cubemaps));
ld->num_pixmaps = ld->params.mipmaps * 6;
break;
default: UNREACHABLE;
}
ld->params.width = zero_level.orig_width;
ld->params.height = zero_level.orig_height;
return true;
}
static bool texture_loader_basisu_load_pixmap(
const char *ctx,
struct basisu_load_data *bld,
TextureLoadData *ld,
basist_transcode_level_params *parm,
Pixmap *out_pixmap
) {
basist_image_level_desc level_desc;
TRY_BOOL(
basist_transcoder_get_image_level_desc,
bld->tc, parm->image_index, parm->level_index, &level_desc
);
struct basis_size_info size_info = texture_loader_basisu_get_transcoded_size_info(
ld, bld->tc, parm->image_index, parm->level_index, parm->format
);
if(size_info.block_size == 0) {
return false;
}
BASISU_DEBUG("Image %i Level %i [%ix%i] : %i * %i = %i",
parm->image_index, parm->level_index,
level_desc.orig_width, level_desc.orig_height,
size_info.num_blocks, size_info.block_size,
size_info.num_blocks * size_info.block_size
);
uint32_t data_size = size_info.num_blocks * size_info.block_size;
if(!texture_loader_basisu_load_cached(
bld->basis_hash,
parm,
&level_desc,
bld->px_decode_format,
bld->px_origin,
data_size,
out_pixmap
)) {
if(!bld->transcoding_started) {
TRY_BOOL(basist_transcoder_start_transcoding, bld->tc);
bld->transcoding_started = true;
}
out_pixmap->data_size = data_size;
out_pixmap->data.untyped = calloc(1, out_pixmap->data_size);
parm->output_blocks = out_pixmap->data.untyped;
parm->output_blocks_size = size_info.num_blocks;
TRY_BOOL(basist_transcoder_transcode_image_level, bld->tc, parm);
out_pixmap->format = bld->px_decode_format;
out_pixmap->width = level_desc.orig_width;
out_pixmap->height = level_desc.orig_height;
out_pixmap->origin = bld->px_origin;
texture_loader_basisu_cache(bld->basis_hash, parm, &level_desc, out_pixmap);
}
if(bld->is_uncompressed_fallback) {
// TODO: maybe cache the swizzle result?
if(!bld->swizzle_supported) {
pixmap_swizzle_inplace(out_pixmap, ld->params.swizzle);
}
// NOTE: it doesn't hurt to call this in the compressed case as well,
// but it's effectively a no-op with a redundant texture type query.
if(!texture_loader_prepare_pixmaps(
ld, out_pixmap, NULL, ld->params.type, ld->params.flags
)) {
return false;
}
}
return true;
}
void texture_loader_basisu(TextureLoadData *ld) {
struct basisu_load_data bld = { 0 };
if(!(bld.tc = texture_loader_basisu_get_transcoder())) {
if(UNLIKELY(!(bld.tc = texture_loader_basisu_get_transcoder()))) {
texture_loader_basisu_failed(ld, &bld);
return;
}
const char *ctx = ld->st->name;
const char *basis_file = ld->tex_src_path;
const char *basis_file = ld->src_paths.main;
SDL_RWops *rw_in = vfs_open(basis_file, VFS_MODE_READ);
if(!rw_in) {
if(!UNLIKELY(rw_in)) {
log_error("%s: VFS error: %s", ctx, vfs_get_error());
texture_loader_basisu_failed(ld, &bld);
return;
}
size_t filesize;
char basis_hash[BASISU_HASH_SIZE];
bld.filebuf = read_basis_file(rw_in, &filesize, sizeof(basis_hash), basis_hash);
bld.filebuf = read_basis_file(rw_in, &filesize, sizeof(bld.basis_hash), bld.basis_hash);
SDL_RWclose(rw_in);
if(UNLIKELY(!bld.filebuf)) {
log_error("%s: Read error: %s", ld->tex_src_path, SDL_GetError());
log_error("%s: Read error: %s", basis_file, SDL_GetError());
texture_loader_basisu_failed(ld, &bld);
return;
}
#define TRY(func, ...) \
if(!(func(__VA_ARGS__))) { \
log_error("%s: " #func "() failed", ctx); \
texture_loader_basisu_failed(ld, &bld); \
return; \
}
assert(!basist_transcoder_get_ready_to_transcode(bld.tc));
basist_transcoder_set_data(bld.tc, (basist_data) { .data = bld.filebuf, .size = filesize });
@ -521,36 +780,52 @@ void texture_loader_basisu(TextureLoadData *ld) {
BASISU_DEBUG("Userdata0: 0x%08x", file_info.userdata.userdata[0]);
BASISU_DEBUG("Userdata1: 0x%08x", file_info.userdata.userdata[1]);
if(file_info.tex_type != BASIST_TYPE_2D) {
log_error("%s: Unsupported Basis Universal texture type %s",
ctx,
basist_get_texture_type_name(file_info.tex_type)
);
}
if(file_info.total_images < 1) {
log_error("%s: No images in Basis Universal texture", ctx);
texture_loader_basisu_failed(ld, &bld);
return;
}
uint32_t image_idx = 0;
uint num_load_images;
if(file_info.total_images > 1) {
log_warn("%s: Basis Universal texture contains more than 1 image; only image %u will be used", ctx, image_idx);
switch(file_info.tex_type) {
case BASIST_TYPE_2D:
num_load_images = 1;
if(file_info.total_images > num_load_images) {
log_warn("%s: Basis Universal texture contains more than 1 image; only image 0 will be used", ctx);
}
ld->params.class = TEXTURE_CLASS_2D;
break;
case BASIST_TYPE_CUBEMAP_ARRAY:
num_load_images = 6;
if(file_info.total_images < num_load_images) {
log_error("%s: Cubemap contains only %u faces; need 6",
ctx, file_info.total_images
);
texture_loader_basisu_failed(ld, &bld);
return;
}
if(file_info.total_images > num_load_images) {
log_warn("%s: Basis Universal cubemap contains more than 6 faces; only first 6 will be used", ctx);
}
ld->params.class = TEXTURE_CLASS_CUBEMAP;
break;
default:
log_error("%s: Unsupported Basis Universal texture type %s",
ctx, basist_get_texture_type_name(file_info.tex_type)
);
texture_loader_basisu_failed(ld, &bld);
return;
}
basist_image_info image_info;
TRY(basist_transcoder_get_image_info, bld.tc, image_idx, &image_info);
BASISU_DEBUG("Size: %ux%u", image_info.width, image_info.height);
BASISU_DEBUG("Original size: %ux%u", image_info.orig_width, image_info.orig_height);
if(image_info.total_levels < 1) {
log_error("%s: No levels in image %u in Basis Universal texture", ctx, image_idx);
texture_loader_basisu_failed(ld, &bld);
return;
}
file_info.total_images = num_load_images;
uint32_t taisei_meta = file_info.userdata.userdata[0];
@ -572,7 +847,7 @@ void texture_loader_basisu(TextureLoadData *ld) {
ld->params.flags &= ~TEX_FLAG_SRGB;
}
PixmapOrigin px_origin = file_info.y_flipped ? PIXMAP_ORIGIN_BOTTOMLEFT : PIXMAP_ORIGIN_TOPLEFT;
bld.px_origin = file_info.y_flipped ? PIXMAP_ORIGIN_BOTTOMLEFT : PIXMAP_ORIGIN_TOPLEFT;
PixmapFormat px_fallback_format = PIXMAP_FORMAT_RGBA8;
basist_texture_format basis_fallback_format = BASIST_FORMAT_RGBA32;
@ -582,7 +857,7 @@ void texture_loader_basisu(TextureLoadData *ld) {
ld,
taisei_meta,
file_info.source_format,
px_origin,
bld.px_origin,
ld->preferred_format ? ld->preferred_format : px_fallback_format,
&qr
);
@ -595,184 +870,58 @@ void texture_loader_basisu(TextureLoadData *ld) {
basist_transcode_level_params p = { 0 };
basist_init_transcode_level_params(&p);
p.image_index = image_idx;
PixmapFormat px_decode_format;
bool is_uncompressed_fallback;
if(pixmap_format_is_compressed(choosen_format)) {
px_decode_format = choosen_format;
bld.px_decode_format = choosen_format;
bld.is_uncompressed_fallback = false;
p.format = compfmt_pixmap_to_basist(choosen_format);
is_uncompressed_fallback = false;
} else {
px_decode_format = px_fallback_format;
bld.px_decode_format = px_fallback_format;
bld.is_uncompressed_fallback = true;
p.format = basis_fallback_format;
is_uncompressed_fallback = true;
}
if(!is_uncompressed_fallback) {
/*
* NOTE: We assume the renderer wants all mip levels of compressed textures to have dimensions divisible by 4
* (if dimension > 2). This is true for most (all?) BC[1-7] implementations in GLES, but other formats and/or
* renderers may have more lax requirements (core GL with ARB_texture_compression_{s3tc,bptc} in particular),
* or even stricter requirements.
*
* TODO: Add a renderer API to query such requirements on a per-format basis, and use it here.
*/
assume(file_info.total_images >= 1);
basist_image_info img_infos[file_info.total_images];
for(uint i = 1; i < image_info.total_levels; ++i) {
basist_image_level_desc level_desc;
TRY(basist_transcoder_get_image_level_desc, bld.tc, p.image_index, i, &level_desc);
if(
(level_desc.orig_width > 2 && level_desc.orig_width % 4) ||
(level_desc.orig_height > 2 && level_desc.orig_height % 4)
) {
log_warn("%s: Mip level %i dimensions are not multiples of 4 (%ix%i); number of levels reduced %i -> %i",
ctx, i, level_desc.orig_width, level_desc.orig_height, image_info.total_levels, i
);
image_info.total_levels = i;
break;
}
}
for(uint i = 0; i < ARRAY_SIZE(img_infos); ++i) {
TRY(basist_transcoder_get_image_info, bld.tc, i, img_infos + i);
}
int mip_bias = env_get_int("TAISEI_BASISU_MIP_BIAS", 0);
mip_bias = iclamp(mip_bias, 0, image_info.total_levels - 1);
TRY_SILENT(texture_loader_basisu_check_consistency, ctx, bld.tc, &file_info, img_infos);
TRY_SILENT(texture_loader_basisu_init_mipmaps, ctx, &bld, ld, &img_infos[0]);
texture_loader_basisu_set_swizzle(ld, bld.px_decode_format, taisei_meta);
{
int max_levels = env_get("TAISEI_BASISU_MAX_MIP_LEVELS", 0);
if(max_levels > 0) {
image_info.total_levels = iclamp(image_info.total_levels, 1, mip_bias + max_levels);
}
}
bld.swizzle_supported = r_supports(RFEAT_TEXTURE_SWIZZLE);
bld.transcoding_started = false;
// NOTE: the 0th mip level is stored in ld->pixmap
// ld->mipmaps and ld->num_mipmaps are for extra mip levels
ld->num_mipmaps = image_info.total_levels - 1 - mip_bias;
// But in TextureParams, the mipmap count includes the 0th level
ld->params.mipmaps = image_info.total_levels - mip_bias;
ld->params.mipmap_mode = TEX_MIPMAP_MANUAL;
uint max_mips = r_texture_util_max_num_miplevels(image_info.orig_width, image_info.orig_height);
if(
ld->params.mipmaps > 1 &&
ld->params.mipmaps < max_mips &&
!r_supports(RFEAT_PARTIAL_MIPMAPS)
) {
log_warn(
"%s: Texture has partial mipmaps (%i levels out of %i), but the renderer doesn't support this. "
"Mipmapping will be disabled for this texture",
ctx, ld->params.mipmaps, max_mips
);
ld->num_mipmaps = 0;
ld->params.mipmaps = 1;
// TODO: In case of decompression fallback, we could switch to auto-generated mipmaps here instead.
// Untested code follows:
#if 0
if(is_uncompressed_fallback) {
ld->num_mipmaps = max_mips;
ld->params.mipmap_mode = TEX_MIPMAP_AUTO;
}
#endif
}
if(ld->num_mipmaps) {
ld->mipmaps = calloc(ld->num_mipmaps, sizeof(*ld->mipmaps));
} else {
ld->mipmaps = NULL;
}
texture_loader_basisu_set_swizzle(ld, px_decode_format, taisei_meta);
bool swizzle_supported = r_supports(RFEAT_TEXTURE_SWIZZLE);
bool transcoding_started = false;
for(uint i = mip_bias; i < ld->params.mipmaps + mip_bias; ++i) {
p.level_index = i;
basist_image_level_desc level_desc;
TRY(basist_transcoder_get_image_level_desc, bld.tc, p.image_index, p.level_index, &level_desc);
Pixmap *out_pixmap = NULL;
if(i > mip_bias) {
out_pixmap = &ld->mipmaps[i - 1 - mip_bias];
} else {
out_pixmap = &ld->pixmap;
ld->params.width = level_desc.orig_width;
ld->params.height = level_desc.orig_height;
}
struct basis_size_info size_info = texture_loader_basisu_get_transcoded_size_info(
ld, bld.tc, p.image_index, p.level_index, p.format
);
if(size_info.block_size == 0) {
texture_loader_basisu_failed(ld, &bld);
return;
}
BASISU_DEBUG("Level %i [%ix%i] : %i * %i = %i",
i,
level_desc.orig_width, level_desc.orig_height,
size_info.num_blocks, size_info.block_size,
size_info.num_blocks * size_info.block_size
);
uint32_t data_size = size_info.num_blocks * size_info.block_size;
if(!texture_loader_basisu_load_cached(
basis_hash,
&p,
&level_desc,
px_decode_format,
px_origin,
data_size,
out_pixmap
)) {
if(!transcoding_started) {
TRY(basist_transcoder_start_transcoding, bld.tc);
transcoding_started = true;
switch(ld->params.class) {
case TEXTURE_CLASS_2D: {
p.image_index = 0;
for(uint mip = 0; mip < ld->params.mipmaps; ++mip) {
p.level_index = mip + bld.mip_bias;
TRY_SILENT(texture_loader_basisu_load_pixmap, ctx, &bld, ld, &p, ld->pixmaps + mip);
}
out_pixmap->data_size = data_size;
out_pixmap->data.untyped = calloc(1, out_pixmap->data_size);
p.output_blocks = out_pixmap->data.untyped;
p.output_blocks_size = size_info.num_blocks;
TRY(basist_transcoder_transcode_image_level, bld.tc, &p);
out_pixmap->format = px_decode_format;
out_pixmap->width = level_desc.orig_width;
out_pixmap->height = level_desc.orig_height;
out_pixmap->origin = px_origin;
texture_loader_basisu_cache(basis_hash, &p, &level_desc, out_pixmap);
break;
}
if(is_uncompressed_fallback) {
// TODO: maybe cache the swizzle result?
if(!swizzle_supported) {
pixmap_swizzle_inplace(out_pixmap, ld->params.swizzle);
case TEXTURE_CLASS_CUBEMAP:
for(int face = 0; face < 6; ++face) {
p.image_index = face;
for(uint mip = 0; mip < ld->params.mipmaps; ++mip) {
p.level_index = mip + bld.mip_bias;
Pixmap *px = &ld->cubemaps[mip].faces[face];
TRY_SILENT(texture_loader_basisu_load_pixmap, ctx, &bld, ld, &p, px);
}
}
// NOTE: it doesn't hurt to call this in the compressed case as well,
// but it's effectively a no-op with a redundant texture type query.
break;
if(!texture_loader_prepare_pixmaps(ld, out_pixmap, NULL, ld->params.type, ld->params.flags)) {
texture_loader_basisu_failed(ld, &bld);
return;
}
}
default: UNREACHABLE;
}
if(is_uncompressed_fallback && !swizzle_supported) {
if(bld.is_uncompressed_fallback && !bld.swizzle_supported) {
ld->params.swizzle = (SwizzleMask) { "rgba" };
}

View file

@ -12,28 +12,29 @@
#include "basisu.h"
void texture_loader_cleanup_stage1(TextureLoadData *ld) {
free(ld->tex_src_path_allocated);
ld->tex_src_path_allocated = NULL;
free(ld->src_paths.main);
ld->src_paths.main = NULL;
free(ld->alphamap_src_path_allocated);
ld->alphamap_src_path_allocated = NULL;
free(ld->src_paths.alphamap);
ld->src_paths.alphamap = NULL;
for(int i = 0; i < ARRAY_SIZE(ld->src_paths.cubemap); ++i) {
free(ld->src_paths.cubemap[i]);
ld->src_paths.cubemap[i] = NULL;
}
}
void texture_loader_cleanup_stage2(TextureLoadData *ld) {
free(ld->pixmap.data.untyped);
ld->pixmap.data.untyped = NULL;
free(ld->alphamap.data.untyped);
ld->alphamap.data.untyped = NULL;
free(ld->pixmap_alphamap.data.untyped);
ld->pixmap_alphamap.data.untyped = NULL;
if(ld->mipmaps) {
for(uint i = 0; i < ld->num_mipmaps; ++i) {
free(ld->mipmaps[i].data.untyped);
if(ld->pixmaps) {
for(int i = 0; i < ld->num_pixmaps; ++i) {
free(ld->pixmaps[i].data.untyped);
}
free(ld->mipmaps);
ld->mipmaps = NULL;
ld->num_mipmaps = 0;
free(ld->pixmaps);
ld->pixmaps = NULL;
}
}
@ -75,6 +76,33 @@ bool texture_loader_check_path(const char *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,
@ -344,6 +372,162 @@ static bool is_preprocess_needed(TextureLoadData *ld) {
);
}
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 void texture_loader_cubemap_from_pixmaps(TextureLoadData *ld) {
ResourceLoadState *st = ld->st;
static_assert_nomsg(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 = calloc(1, sizeof(*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(!pixmap_load_file(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) {
@ -363,12 +547,12 @@ void texture_loader_stage1(ResourceLoadState *st) {
},
.preprocess.multiply_alpha = true,
.st = st,
.tex_src_path = st->path,
};
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;
@ -376,8 +560,15 @@ void texture_loader_stage1(ResourceLoadState *st) {
char *str_format = NULL;
if(!parse_keyvalue_file_with_spec(st->path, (KVSpec[]) {
{ "source", .out_str = &ld->tex_src_path_allocated },
{ "alphamap", .out_str = &ld->alphamap_src_path_allocated },
{ "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 },
@ -393,23 +584,48 @@ void texture_loader_stage1(ResourceLoadState *st) {
return;
}
if(!ld->tex_src_path_allocated) {
ld->tex_src_path_allocated = texture_loader_source_path(st->name);
bool class_ok = texture_loader_parse_class(ld, str_class, &ld->params.class);
free(str_class);
if(!ld->tex_src_path_allocated) {
log_error("%s: Couldn't infer source path from texture name", st->name);
} else {
log_info("%s: Inferred source path from texture name: %s", st->name, ld->tex_src_path_allocated);
}
if(!ld->tex_src_path_allocated) {
texture_loader_failed(ld);
return;
}
if(!class_ok) {
texture_loader_failed(ld);
return;
}
ld->tex_src_path = ld->tex_src_path_allocated;
ld->alphamap_src_path = ld->alphamap_src_path_allocated;
#define BADKEY(key, var, cstr) do { \
if((var) != NULL) { \
log_warn("%s: `" key "` is not applicable for " cstr, st->name); \
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;
}
texture_loader_parse_filter(ld, "filter_min", str_filter_min, &ld->params.filter.min, true);
free(str_filter_min);
@ -427,52 +643,62 @@ void texture_loader_stage1(ResourceLoadState *st) {
free(str_format);
if(!format_ok) {
log_error("%s: Bad or unsupported pixel format specification", st->path);
texture_loader_failed(ld);
return;
}
} else {
ld->src_paths.main = strdup(st->path);
}
if(want_srgb) {
ld->params.flags |= TEX_FLAG_SRGB;
}
assert(!ld->preprocess.linearize);
if(texture_loader_basisu_check_path(ld->tex_src_path)) {
free(ld->alphamap_src_path_allocated);
ld->alphamap_src_path = ld->alphamap_src_path_allocated = NULL;
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)) {
free(ld->src_paths.alphamap);
ld->src_paths.alphamap = NULL;
texture_loader_basisu(ld);
return;
}
if(!pixmap_load_file(ld->tex_src_path, &ld->pixmap, ld->preferred_format)) {
log_error("%s: Couldn't load texture image %s", st->name, ld->tex_src_path);
ld->num_pixmaps = 1;
ld->pixmaps = calloc(1, sizeof(*ld->pixmaps));
if(!pixmap_load_file(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->alphamap_src_path && !pixmap_load_file(ld->alphamap_src_path, &ld->pixmap_alphamap, PIXMAP_FORMAT_R8)) {
log_error("%s: Couldn't load texture alphamap %s", st->name, ld->alphamap_src_path);
if(ld->src_paths.alphamap && !pixmap_load_file(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->pixmap.format;
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->pixmap.format, ld->pixmap.origin, NULL);
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->pixmap, &ld->pixmap_alphamap, ld->params.type, ld->params.flags)) {
if(!texture_loader_prepare_pixmaps(ld, ld->pixmaps, &ld->alphamap, ld->params.type, ld->params.flags)) {
texture_loader_failed(ld);
return;
}
if(ld->pixmap_alphamap.data.untyped) {
if(ld->alphamap.data.untyped) {
ld->preprocess.apply_alphamap = true;
}
@ -480,8 +706,8 @@ void texture_loader_stage1(ResourceLoadState *st) {
ld->preprocess.multiply_alpha = false;
}
ld->params.width = ld->pixmap.width;
ld->params.height = ld->pixmap.height;
ld->params.width = ld->pixmaps->width;
ld->params.height = ld->pixmaps->height;
texture_loader_continue(ld);
}
@ -499,7 +725,22 @@ void texture_loader_continue(TextureLoadData *ld) {
}
}
if(ld->mipmaps) {
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) {
@ -507,7 +748,7 @@ void texture_loader_continue(TextureLoadData *ld) {
memset(&ld->preprocess, 0, sizeof(ld->preprocess));
}
assume(ld->pixmap_alphamap.data.untyped == NULL);
assume(ld->alphamap.data.untyped == NULL);
assert(ld->params.mipmap_mode == TEX_MIPMAP_MANUAL);
} else {
ld->params.mipmap_mode = TEX_MIPMAP_AUTO;
@ -562,13 +803,12 @@ static void texture_loader_stage2(ResourceLoadState *st) {
assert(!preprocess_needed);
}
if(ld->mipmaps) {
assert(ld->pixmaps != NULL);
assert(ld->num_pixmaps > 0);
if(ld->num_pixmaps > 1) {
assert(!preprocess_needed);
assert(ld->pixmap_alphamap.data.untyped == NULL);
assert(ld->params.mipmap_mode == TEX_MIPMAP_MANUAL);
assert(ld->num_mipmaps > 0);
} else {
assert(ld->num_mipmaps == 0);
assert(ld->alphamap.data.untyped == NULL);
}
Texture *texture;
@ -593,25 +833,46 @@ static void texture_loader_stage2(ResourceLoadState *st) {
r_texture_set_debug_label(texture, st->name);
}
r_texture_fill(texture, 0, &ld->pixmap);
free(ld->pixmap.data.untyped);
ld->pixmap.data.untyped = NULL;
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);
free(p->data.untyped);
}
for(uint i = 0; i < ld->num_mipmaps; ++i) {
r_texture_fill(texture, i + 1, &ld->mipmaps[i]);
free(ld->mipmaps[i].data.untyped);
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); \
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
}
free(ld->cubemaps);
ld->cubemaps = NULL;
} else {
UNREACHABLE;
}
free(ld->mipmaps);
ld->mipmaps = NULL;
Texture *alphamap = NULL;
if(ld->pixmap_alphamap.data.untyped) {
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->pixmap_alphamap.format);
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;
@ -621,12 +882,13 @@ static void texture_loader_stage2(ResourceLoadState *st) {
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, &ld->pixmap_alphamap);
free(ld->pixmap_alphamap.data.untyped);
ld->pixmap_alphamap.data.untyped = NULL;
r_texture_fill(alphamap, 0, 0, &ld->alphamap);
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);
}

View file

@ -15,12 +15,18 @@
#include "resource/resource.h"
#include "resource/texture.h"
typedef struct TextureLoadData {
Pixmap pixmap;
Pixmap pixmap_alphamap;
typedef struct TextureLoadCubemap {
Pixmap faces[6];
} TextureLoadCubemap;
uint num_mipmaps;
Pixmap *mipmaps;
typedef struct TextureLoadData {
Pixmap alphamap;
union {
Pixmap *pixmaps;
TextureLoadCubemap *cubemaps;
};
uint num_pixmaps;
TextureParams params;
@ -34,10 +40,11 @@ typedef struct TextureLoadData {
// NOTE: Despite being a PixmapFormat, this is also used to pick a proper TextureType later. Irrelevant for compressed textures, unless decompression fallback is used.
PixmapFormat preferred_format;
const char *tex_src_path;
const char *alphamap_src_path;
char *tex_src_path_allocated;
char *alphamap_src_path_allocated;
struct {
char *main;
char *alphamap;
char *cubemap[6];
} src_paths;
ResourceLoadState *st;
} TextureLoadData;