diff --git a/resources/00-taisei.pkgdir/shader/lib/util.glslh b/resources/00-taisei.pkgdir/shader/lib/util.glslh index 4e2f44dc..a966b824 100644 --- a/resources/00-taisei.pkgdir/shader/lib/util.glslh +++ b/resources/00-taisei.pkgdir/shader/lib/util.glslh @@ -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 */ diff --git a/src/renderer/api.c b/src/renderer/api.c index c98c8a06..d1d5175b 100644 --- a/src/renderer/api.c +++ b/src/renderer/api.c @@ -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) { diff --git a/src/renderer/api.h b/src/renderer/api.h index e6f74f8a..06da896d 100644 --- a/src/renderer/api.h +++ b/src/renderer/api.h @@ -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); diff --git a/src/renderer/common/backend.h b/src/renderer/common/backend.h index 081212ed..f97b416e 100644 --- a/src/renderer/common/backend.h +++ b/src/renderer/common/backend.h @@ -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); diff --git a/src/renderer/gl33/gl33.c b/src/renderer/gl33/gl33.c index 65dc16fa..e4399358 100644 --- a/src/renderer/gl33/gl33.c +++ b/src/renderer/gl33/gl33.c @@ -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) { diff --git a/src/renderer/gl33/shader_program.c b/src/renderer/gl33/shader_program.c index 0d0d9cc0..8605e0b2 100644 --- a/src/renderer/gl33/shader_program.c +++ b/src/renderer/gl33/shader_program.c @@ -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); diff --git a/src/renderer/gl33/texture.c b/src/renderer/gl33/texture.c index ffabef33..315e3b90 100644 --- a/src/renderer/gl33/texture.c +++ b/src/renderer/gl33/texture.c @@ -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; } diff --git a/src/renderer/gl33/texture.h b/src/renderer/gl33/texture.h index 0db14267..00c5be26 100644 --- a/src/renderer/gl33/texture.h +++ b/src/renderer/gl33/texture.h @@ -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); diff --git a/src/renderer/glcommon/opengl.c b/src/renderer/glcommon/opengl.c index 022737d3..849b470f 100644 --- a/src/renderer/glcommon/opengl.c +++ b/src/renderer/glcommon/opengl.c @@ -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(); diff --git a/src/renderer/glcommon/opengl.h b/src/renderer/glcommon/opengl.h index 7464e7cb..d9b4ddd2 100644 --- a/src/renderer/glcommon/opengl.h +++ b/src/renderer/glcommon/opengl.h @@ -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; diff --git a/src/renderer/null/null.c b/src/renderer/null/null.c index 4ce5d740..05974933 100644 --- a/src/renderer/null/null.c +++ b/src/renderer/null/null.c @@ -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) { } diff --git a/src/resource/font.c b/src/resource/font.c index 0f0064c7..8e8119f8 100644 --- a/src/resource/font.c +++ b/src/resource/font.c @@ -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 diff --git a/src/resource/texture_loader/basisu.c b/src/resource/texture_loader/basisu.c index ca5c5db0..9fbed447 100644 --- a/src/resource/texture_loader/basisu.c +++ b/src/resource/texture_loader/basisu.c @@ -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" }; } diff --git a/src/resource/texture_loader/texture_loader.c b/src/resource/texture_loader/texture_loader.c index d9b162fc..0c733a06 100644 --- a/src/resource/texture_loader/texture_loader.c +++ b/src/resource/texture_loader/texture_loader.c @@ -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); } diff --git a/src/resource/texture_loader/texture_loader.h b/src/resource/texture_loader/texture_loader.h index 5195c29b..6279c702 100644 --- a/src/resource/texture_loader/texture_loader.h +++ b/src/resource/texture_loader/texture_loader.h @@ -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;