From 400bc2cc267a6e5a3dd75da152c455af21226772 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 15 Jul 2016 11:47:49 +0200 Subject: [PATCH] Support .ktx textures, ETC1/ETC2/EAC compression --- panda/src/framework/windowFramework.cxx | 12 +- panda/src/gles2gsg/gles2gsg.h | 10 + panda/src/glesgsg/glesgsg.h | 11 + .../glstuff/glGraphicsStateGuardian_src.cxx | 160 ++- panda/src/glstuff/glShaderContext_src.cxx | 9 + panda/src/gobj/texture.I | 23 +- panda/src/gobj/texture.cxx | 1086 ++++++++++++++++- panda/src/gobj/texture.h | 9 + panda/src/gobj/texturePool.cxx | 7 +- 9 files changed, 1293 insertions(+), 34 deletions(-) diff --git a/panda/src/framework/windowFramework.cxx b/panda/src/framework/windowFramework.cxx index 7ac9e89fa2..898aae50a1 100644 --- a/panda/src/framework/windowFramework.cxx +++ b/panda/src/framework/windowFramework.cxx @@ -602,16 +602,10 @@ load_model(const NodePath &parent, Filename filename) { if (model_type == (LoaderFileType *)NULL) { // The extension isn't a known model file type; is it a known image file // extension? - if (extension == "txo" || downcase(extension) == "dds") { - // A texture object. Not exactly an image, but certainly a texture. + TexturePool *texture_pool = TexturePool::get_global_ptr(); + if (texture_pool->get_texture_type(extension) != NULL) { + // It is a known image file extension. is_image = true; - - } else { - TexturePool *texture_pool = TexturePool::get_global_ptr(); - if (texture_pool->get_texture_type(extension) != NULL) { - // It is a known image file extension. - is_image = true; - } } } } diff --git a/panda/src/gles2gsg/gles2gsg.h b/panda/src/gles2gsg/gles2gsg.h index f88d40c3fc..8d80e690eb 100644 --- a/panda/src/gles2gsg/gles2gsg.h +++ b/panda/src/gles2gsg/gles2gsg.h @@ -231,6 +231,16 @@ typedef char GLchar; #define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311 #define GL_FRAMEBUFFER_DEFAULT_SAMPLES 0x9313 #define GL_ALL_BARRIER_BITS 0xFFFFFFFF +#define GL_COMPRESSED_R11_EAC 0x9270 +#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +#define GL_COMPRESSED_RG11_EAC 0x9272 +#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_SRGB8_ETC2 0x9275 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 #undef SUPPORT_IMMEDIATE_MODE #define APIENTRY diff --git a/panda/src/glesgsg/glesgsg.h b/panda/src/glesgsg/glesgsg.h index 416e5737cb..c0afb58b56 100644 --- a/panda/src/glesgsg/glesgsg.h +++ b/panda/src/glesgsg/glesgsg.h @@ -132,6 +132,17 @@ #define GL_DEBUG_SEVERITY_LOW 0x9148 #define GL_DEBUG_OUTPUT 0x92E0 +#define GL_COMPRESSED_R11_EAC 0x9270 +#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +#define GL_COMPRESSED_RG11_EAC 0x9272 +#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_SRGB8_ETC2 0x9275 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 + #undef SUPPORT_IMMEDIATE_MODE #define APIENTRY #define APIENTRYP * diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index d21c4df57b..ee24b407d4 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -1053,6 +1053,24 @@ reset() { break; #endif + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_RG11_EAC: + _compressed_texture_formats.set_bit(Texture::CM_eac); + break; + + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + _compressed_texture_formats.set_bit(Texture::CM_etc1); + _compressed_texture_formats.set_bit(Texture::CM_etc2); + break; + +#ifdef OPENGLES + case GL_ETC1_RGB8_OES: + _compressed_texture_formats.set_bit(Texture::CM_etc1); + break; +#endif + default: break; } @@ -8066,6 +8084,12 @@ get_texture_target(Texture::TextureType texture_type) const { return GL_TEXTURE_1D; #endif + case Texture::TT_1d_texture_array: + // There are no 1D array textures in OpenGL ES. Fall back to 2D textures. +#ifndef OPENGLES + return GL_TEXTURE_1D_ARRAY; +#endif + case Texture::TT_2d_texture: return GL_TEXTURE_2D; @@ -8282,6 +8306,11 @@ get_component_type(Texture::ComponentType component_type) { return GL_HALF_FLOAT; #endif +#ifndef OPENGLES_1 + case Texture::T_unsigned_int: + return GL_UNSIGNED_INT; +#endif + default: GLCAT.error() << "Invalid Texture::Type value!\n"; return GL_UNSIGNED_BYTE; @@ -8444,9 +8473,9 @@ get_external_image_format(Texture *tex) const { case Texture::CM_rgtc: #ifndef OPENGLES - if (tex->get_format() == Texture::F_luminance) { + if (format == Texture::F_luminance) { return GL_COMPRESSED_LUMINANCE_LATC1_EXT; - } else if (tex->get_format() == Texture::F_luminance_alpha) { + } else if (format == Texture::F_luminance_alpha) { return GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT; } else if (tex->get_num_components() == 1) { return GL_COMPRESSED_RED_RGTC1; @@ -8456,6 +8485,41 @@ get_external_image_format(Texture *tex) const { #endif break; + case Texture::CM_etc1: +#ifdef OPENGLES + return GL_ETC1_RGB8_OES; +#endif + // Fall through - ETC2 is backward compatible + case Texture::CM_etc2: + if (format == Texture::F_rgbm) { + return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + } else if (format == Texture::F_srgb_alpha) { + return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + } else if (format == Texture::F_srgb) { + return GL_COMPRESSED_SRGB8_ETC2; + } else if (Texture::has_alpha(format)) { + return GL_COMPRESSED_RGBA8_ETC2_EAC; + } else { + return GL_COMPRESSED_RGB8_ETC2; + } + break; + + case Texture::CM_eac: + if (Texture::is_unsigned(tex->get_component_type())) { + if (tex->get_num_components() == 1) { + return GL_COMPRESSED_R11_EAC; + } else { + return GL_COMPRESSED_RG11_EAC; + } + } else { + if (tex->get_num_components() == 1) { + return GL_COMPRESSED_SIGNED_R11_EAC; + } else { + return GL_COMPRESSED_SIGNED_RG11_EAC; + } + } + break; + case Texture::CM_default: case Texture::CM_off: case Texture::CM_dxt2: @@ -8565,7 +8629,7 @@ get_external_image_format(Texture *tex) const { } GLCAT.error() << "Invalid Texture::Format value in get_external_image_format(): " - << tex->get_format() << "\n"; + << format << "\n"; return GL_RGB; } @@ -8615,10 +8679,14 @@ get_internal_image_format(Texture *tex, bool force_sized) const { break; case Texture::F_rgbm: + case Texture::F_rgba5: case Texture::F_rgb10_a2: if (get_supports_compressed_texture_format(Texture::CM_dxt1) && !is_3d) { return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; } + if (get_supports_compressed_texture_format(Texture::CM_etc2) && !is_3d) { + return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + } #ifndef OPENGLES if (get_supports_compressed_texture_format(Texture::CM_fxt1) && !is_3d) { return GL_COMPRESSED_RGBA_FXT1_3DFX; @@ -8633,6 +8701,9 @@ get_internal_image_format(Texture *tex, bool force_sized) const { return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; } #endif + if (get_supports_compressed_texture_format(Texture::CM_etc2) && !is_3d) { + return GL_COMPRESSED_RGBA8_ETC2_EAC; + } #ifndef OPENGLES if (get_supports_compressed_texture_format(Texture::CM_fxt1) && !is_3d) { return GL_COMPRESSED_RGBA_FXT1_3DFX; @@ -8651,6 +8722,9 @@ get_internal_image_format(Texture *tex, bool force_sized) const { return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; } #endif + if (get_supports_compressed_texture_format(Texture::CM_etc2) && !is_3d) { + return GL_COMPRESSED_RGBA8_ETC2_EAC; + } #ifndef OPENGLES if (get_supports_compressed_texture_format(Texture::CM_fxt1) && !is_3d) { return GL_COMPRESSED_RGBA_FXT1_3DFX; @@ -8661,7 +8735,6 @@ get_internal_image_format(Texture *tex, bool force_sized) const { case Texture::F_rgb: case Texture::F_rgb5: - case Texture::F_rgba5: case Texture::F_rgb8: case Texture::F_rgb12: case Texture::F_rgb332: @@ -8670,7 +8743,14 @@ get_internal_image_format(Texture *tex, bool force_sized) const { if (get_supports_compressed_texture_format(Texture::CM_dxt1) && !is_3d) { return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; } -#ifndef OPENGLES + if (get_supports_compressed_texture_format(Texture::CM_etc2) && !is_3d) { + return GL_COMPRESSED_RGB8_ETC2; + } +#ifdef OPENGLES + if (get_supports_compressed_texture_format(Texture::CM_etc1) && !is_3d) { + return GL_ETC1_RGB8_OES; + } +#else if (get_supports_compressed_texture_format(Texture::CM_fxt1) && !is_3d) { return GL_COMPRESSED_RGB_FXT1_3DFX; } @@ -8702,6 +8782,13 @@ get_internal_image_format(Texture *tex, bool force_sized) const { return GL_COMPRESSED_RED_RGTC1; } #endif + if (get_supports_compressed_texture_format(Texture::CM_eac) && !is_3d) { + if (Texture::is_unsigned(tex->get_component_type())) { + return GL_COMPRESSED_R11_EAC; + } else { + return GL_COMPRESSED_SIGNED_R11_EAC; + } + } if (get_supports_compressed_texture_format(Texture::CM_dxt1) && !is_3d) { return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; } @@ -8721,6 +8808,13 @@ get_internal_image_format(Texture *tex, bool force_sized) const { return GL_COMPRESSED_RG_RGTC2; } #endif + if (get_supports_compressed_texture_format(Texture::CM_eac) && !is_3d) { + if (Texture::is_unsigned(tex->get_component_type())) { + return GL_COMPRESSED_RG11_EAC; + } else { + return GL_COMPRESSED_SIGNED_RG11_EAC; + } + } if (get_supports_compressed_texture_format(Texture::CM_dxt1) && !is_3d) { return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; } @@ -8764,12 +8858,18 @@ get_internal_image_format(Texture *tex, bool force_sized) const { if (get_supports_compressed_texture_format(Texture::CM_dxt1) && !is_3d) { return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT; } + if (get_supports_compressed_texture_format(Texture::CM_etc2) && !is_3d) { + return GL_COMPRESSED_SRGB8_ETC2; + } return GL_COMPRESSED_SRGB; case Texture::F_srgb_alpha: if (get_supports_compressed_texture_format(Texture::CM_dxt5) && !is_3d) { return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; } + if (get_supports_compressed_texture_format(Texture::CM_etc2) && !is_3d) { + return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + } return GL_COMPRESSED_SRGB_ALPHA; case Texture::F_sluminance: @@ -8855,9 +8955,9 @@ get_internal_image_format(Texture *tex, bool force_sized) const { case Texture::CM_rgtc: #ifndef OPENGLES - if (tex->get_format() == Texture::F_luminance) { + if (format == Texture::F_luminance) { return GL_COMPRESSED_LUMINANCE_LATC1_EXT; - } else if (tex->get_format() == Texture::F_luminance_alpha) { + } else if (format == Texture::F_luminance_alpha) { return GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT; } else if (tex->get_num_components() == 1) { return GL_COMPRESSED_RED_RGTC1; @@ -8867,6 +8967,41 @@ get_internal_image_format(Texture *tex, bool force_sized) const { #endif break; + case Texture::CM_etc1: +#ifdef OPENGLES + return GL_ETC1_RGB8_OES; +#endif + // Fall through - ETC2 is backward compatible + case Texture::CM_etc2: + if (format == Texture::F_rgbm) { + return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + } else if (format == Texture::F_srgb_alpha) { + return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + } else if (format == Texture::F_srgb) { + return GL_COMPRESSED_SRGB8_ETC2; + } else if (Texture::has_alpha(format)) { + return GL_COMPRESSED_RGBA8_ETC2_EAC; + } else { + return GL_COMPRESSED_RGB8_ETC2; + } + break; + + case Texture::CM_eac: + if (Texture::is_unsigned(tex->get_component_type())) { + if (tex->get_num_components() == 1) { + return GL_COMPRESSED_R11_EAC; + } else { + return GL_COMPRESSED_RG11_EAC; + } + } else { + if (tex->get_num_components() == 1) { + return GL_COMPRESSED_SIGNED_R11_EAC; + } else { + return GL_COMPRESSED_SIGNED_RG11_EAC; + } + } + break; + case Texture::CM_default: case Texture::CM_off: case Texture::CM_dxt2: @@ -9197,7 +9332,7 @@ get_internal_image_format(Texture *tex, bool force_sized) const { default: GLCAT.error() << "Invalid image format in get_internal_image_format(): " - << (int)tex->get_format() << "\n"; + << (int)format << "\n"; return force_sized ? GL_RGB8 : GL_RGB; } } @@ -11661,6 +11796,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) { break; case Texture::TT_2d_texture: case Texture::TT_cube_map: + case Texture::TT_1d_texture_array: _glTexStorage2D(target, num_levels, internal_format, width, height); break; case Texture::TT_3d_texture: @@ -12119,6 +12255,14 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload, // fill it in with the correct clear color, which we can then // upload. ptimage = tex->make_ram_mipmap_image(n); + + } else if (image_compression != Texture::CM_off) { + // We can't upload a NULL compressed texture. + if (_supports_texture_max_level) { + // Tell the GL we have no more mipmaps for it to use. + glTexParameteri(texture_target, GL_TEXTURE_MAX_LEVEL, n - mipmap_bias); + } + break; } } image_ptr = ptimage; diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index 4cdf3749a8..60563af5db 100644 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/panda/src/glstuff/glShaderContext_src.cxx @@ -1296,12 +1296,15 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) { case GL_SAMPLER_2D_ARRAY_SHADOW: #ifndef OPENGLES case GL_INT_SAMPLER_1D: + case GL_INT_SAMPLER_1D_ARRAY: case GL_INT_SAMPLER_BUFFER: case GL_INT_SAMPLER_CUBE_MAP_ARRAY: case GL_UNSIGNED_INT_SAMPLER_1D: + case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: case GL_UNSIGNED_INT_SAMPLER_BUFFER: case GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY: case GL_SAMPLER_1D: + case GL_SAMPLER_1D_ARRAY: case GL_SAMPLER_1D_SHADOW: case GL_SAMPLER_BUFFER: case GL_SAMPLER_CUBE_MAP_ARRAY: @@ -1597,6 +1600,12 @@ get_sampler_texture_type(int &out, GLenum param_type) { case GL_SAMPLER_1D: out = Texture::TT_1d_texture; return true; + + case GL_INT_SAMPLER_1D_ARRAY: + case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: + case GL_SAMPLER_1D_ARRAY: + out = Texture::TT_1d_texture_array; + return true; #endif case GL_INT_SAMPLER_2D: diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I index b608a39c00..47ce9e233f 100644 --- a/panda/src/gobj/texture.I +++ b/panda/src/gobj/texture.I @@ -2318,8 +2318,8 @@ get_unsigned_short(const unsigned char *&p) { } /** - * Returns true if the indicated filename ends in .txo or .txo.pz, false - * otherwise. + * Returns true if the indicated filename ends in .txo or .txo.pz or .txo.gz, + * false otherwise. */ INLINE bool Texture:: is_txo_filename(const Filename &fullpath) { @@ -2333,8 +2333,8 @@ is_txo_filename(const Filename &fullpath) { } /** - * Returns true if the indicated filename ends in .dds or .dds.pz, false - * otherwise. + * Returns true if the indicated filename ends in .dds or .dds.pz or .dds.gz, + * false otherwise. */ INLINE bool Texture:: is_dds_filename(const Filename &fullpath) { @@ -2347,6 +2347,21 @@ is_dds_filename(const Filename &fullpath) { return (downcase(extension) == "dds"); } +/** + * Returns true if the indicated filename ends in .ktx or .ktx.pz or .ktx.gz, + * false otherwise. + */ +INLINE bool Texture:: +is_ktx_filename(const Filename &fullpath) { + string extension = fullpath.get_extension(); +#ifdef HAVE_ZLIB + if (extension == "pz" || extension == "gz") { + extension = Filename(fullpath.get_basename_wo_extension()).get_extension(); + } +#endif // HAVE_ZLIB + return (downcase(extension) == "ktx"); +} + /** * */ diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index 1802a36133..8b3285337e 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -132,6 +132,231 @@ struct DDSHeader { DDSCaps2 caps; }; +// Stuff to read KTX files. +enum KTXType { + KTX_BYTE = 0x1400, + KTX_UNSIGNED_BYTE = 0x1401, + KTX_SHORT = 0x1402, + KTX_UNSIGNED_SHORT = 0x1403, + KTX_INT = 0x1404, + KTX_UNSIGNED_INT = 0x1405, + KTX_FLOAT = 0x1406, + KTX_HALF_FLOAT = 0x140B, + KTX_UNSIGNED_BYTE_3_3_2 = 0x8032, + KTX_UNSIGNED_SHORT_4_4_4_4 = 0x8033, + KTX_UNSIGNED_SHORT_5_5_5_1 = 0x8034, + KTX_UNSIGNED_INT_8_8_8_8 = 0x8035, + KTX_UNSIGNED_INT_10_10_10_2 = 0x8036, + KTX_UNSIGNED_BYTE_2_3_3_REV = 0x8362, + KTX_UNSIGNED_SHORT_5_6_5 = 0x8363, + KTX_UNSIGNED_SHORT_5_6_5_REV = 0x8364, + KTX_UNSIGNED_SHORT_4_4_4_4_REV = 0x8365, + KTX_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366, + KTX_UNSIGNED_INT_8_8_8_8_REV = 0x8367, + KTX_UNSIGNED_INT_2_10_10_10_REV = 0x8368, + KTX_UNSIGNED_INT_24_8 = 0x84FA, + KTX_UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B, + KTX_UNSIGNED_INT_5_9_9_9_REV = 0x8C3E, + KTX_FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD, +}; + +enum KTXFormat { + KTX_ALPHA = 0x1906, + KTX_ALPHA12 = 0x803D, + KTX_ALPHA16 = 0x803E, + KTX_ALPHA16_SNORM = 0x9018, + KTX_ALPHA4 = 0x803B, + KTX_ALPHA8 = 0x803C, + KTX_ALPHA8_SNORM = 0x9014, + KTX_ALPHA_SNORM = 0x9010, + KTX_BGR = 0x80E0, + KTX_BGR_INTEGER = 0x8D9A, + KTX_BGRA = 0x80E1, + KTX_BGRA_INTEGER = 0x8D9B, + KTX_BLUE = 0x1905, + KTX_BLUE_INTEGER = 0x8D96, + KTX_COLOR_INDEX = 0x1900, + KTX_DEPTH24_STENCIL8 = 0x88F0, + KTX_DEPTH32F_STENCIL8 = 0x8CAD, + KTX_DEPTH_COMPONENT = 0x1902, + KTX_DEPTH_COMPONENT16 = 0x81A5, + KTX_DEPTH_COMPONENT24 = 0x81A6, + KTX_DEPTH_COMPONENT32 = 0x81A7, + KTX_DEPTH_COMPONENT32F = 0x8CAC, + KTX_DEPTH_STENCIL = 0x84F9, + KTX_GREEN = 0x1904, + KTX_GREEN_INTEGER = 0x8D95, + KTX_INTENSITY = 0x8049, + KTX_INTENSITY12 = 0x804C, + KTX_INTENSITY16 = 0x804D, + KTX_INTENSITY16_SNORM = 0x901B, + KTX_INTENSITY4 = 0x804A, + KTX_INTENSITY8 = 0x804B, + KTX_INTENSITY8_SNORM = 0x9017, + KTX_INTENSITY_SNORM = 0x9013, + KTX_LUMINANCE = 0x1909, + KTX_LUMINANCE12 = 0x8041, + KTX_LUMINANCE12_ALPHA12 = 0x8047, + KTX_LUMINANCE12_ALPHA4 = 0x8046, + KTX_LUMINANCE16 = 0x8042, + KTX_LUMINANCE16_ALPHA16 = 0x8048, + KTX_LUMINANCE16_ALPHA16_SNORM = 0x901A, + KTX_LUMINANCE16_SNORM = 0x9019, + KTX_LUMINANCE4 = 0x803F, + KTX_LUMINANCE4_ALPHA4 = 0x8043, + KTX_LUMINANCE6_ALPHA2 = 0x8044, + KTX_LUMINANCE8 = 0x8040, + KTX_LUMINANCE8_ALPHA8 = 0x8045, + KTX_LUMINANCE8_ALPHA8_SNORM = 0x9016, + KTX_LUMINANCE8_SNORM = 0x9015, + KTX_LUMINANCE_ALPHA = 0x190A, + KTX_LUMINANCE_ALPHA_SNORM = 0x9012, + KTX_LUMINANCE_SNORM = 0x9011, + KTX_R11F_G11F_B10F = 0x8C3A, + KTX_R16 = 0x822A, + KTX_R16_SNORM = 0x8F98, + KTX_R16F = 0x822D, + KTX_R16I = 0x8233, + KTX_R16UI = 0x8234, + KTX_R32F = 0x822E, + KTX_R32I = 0x8235, + KTX_R32UI = 0x8236, + KTX_R3_G3_B2 = 0x2A10, + KTX_R8 = 0x8229, + KTX_R8_SNORM = 0x8F94, + KTX_R8I = 0x8231, + KTX_R8UI = 0x8232, + KTX_RED = 0x1903, + KTX_RED_INTEGER = 0x8D94, + KTX_RED_SNORM = 0x8F90, + KTX_RG = 0x8227, + KTX_RG16 = 0x822C, + KTX_RG16_SNORM = 0x8F99, + KTX_RG16F = 0x822F, + KTX_RG16I = 0x8239, + KTX_RG16UI = 0x823A, + KTX_RG32F = 0x8230, + KTX_RG32I = 0x823B, + KTX_RG32UI = 0x823C, + KTX_RG8 = 0x822B, + KTX_RG8_SNORM = 0x8F95, + KTX_RG8I = 0x8237, + KTX_RG8UI = 0x8238, + KTX_RG_INTEGER = 0x8228, + KTX_RG_SNORM = 0x8F91, + KTX_RGB = 0x1907, + KTX_RGB10 = 0x8052, + KTX_RGB10_A2 = 0x8059, + KTX_RGB12 = 0x8053, + KTX_RGB16 = 0x8054, + KTX_RGB16_SNORM = 0x8F9A, + KTX_RGB16F = 0x881B, + KTX_RGB16I = 0x8D89, + KTX_RGB16UI = 0x8D77, + KTX_RGB2 = 0x804E, + KTX_RGB32F = 0x8815, + KTX_RGB32I = 0x8D83, + KTX_RGB32UI = 0x8D71, + KTX_RGB4 = 0x804F, + KTX_RGB5 = 0x8050, + KTX_RGB5_A1 = 0x8057, + KTX_RGB8 = 0x8051, + KTX_RGB8_SNORM = 0x8F96, + KTX_RGB8I = 0x8D8F, + KTX_RGB8UI = 0x8D7D, + KTX_RGB9_E5 = 0x8C3D, + KTX_RGB_INTEGER = 0x8D98, + KTX_RGB_SNORM = 0x8F92, + KTX_RGBA = 0x1908, + KTX_RGBA12 = 0x805A, + KTX_RGBA16 = 0x805B, + KTX_RGBA16_SNORM = 0x8F9B, + KTX_RGBA16F = 0x881A, + KTX_RGBA16I = 0x8D88, + KTX_RGBA16UI = 0x8D76, + KTX_RGBA2 = 0x8055, + KTX_RGBA32F = 0x8814, + KTX_RGBA32I = 0x8D82, + KTX_RGBA32UI = 0x8D70, + KTX_RGBA4 = 0x8056, + KTX_RGBA8 = 0x8058, + KTX_RGBA8_SNORM = 0x8F97, + KTX_RGBA8I = 0x8D8E, + KTX_RGBA8UI = 0x8D7C, + KTX_RGBA_INTEGER = 0x8D99, + KTX_RGBA_SNORM = 0x8F93, + KTX_SLUMINANCE = 0x8C46, + KTX_SLUMINANCE8 = 0x8C47, + KTX_SLUMINANCE8_ALPHA8 = 0x8C45, + KTX_SLUMINANCE_ALPHA = 0x8C44, + KTX_SRGB = 0x8C40, + KTX_SRGB8 = 0x8C41, + KTX_SRGB8_ALPHA8 = 0x8C43, + KTX_SRGB_ALPHA = 0x8C42, + KTX_STENCIL_INDEX = 0x1901, + KTX_STENCIL_INDEX1 = 0x8D46, + KTX_STENCIL_INDEX16 = 0x8D49, + KTX_STENCIL_INDEX4 = 0x8D47, + KTX_STENCIL_INDEX8 = 0x8D48, +}; + +enum KTXCompressedFormat { + KTX_COMPRESSED_LUMINANCE_ALPHA_LATC2 = 0x8C72, + KTX_COMPRESSED_LUMINANCE_LATC1 = 0x8C70, + KTX_COMPRESSED_R11_EAC = 0x9270, + KTX_COMPRESSED_RED = 0x8225, + KTX_COMPRESSED_RED_RGTC1 = 0x8DBB, + KTX_COMPRESSED_RG = 0x8226, + KTX_COMPRESSED_RG11_EAC = 0x9272, + KTX_COMPRESSED_RG_RGTC2 = 0x8DBD, + KTX_COMPRESSED_RGB = 0x84ED, + KTX_COMPRESSED_RGB8_ETC2 = 0x9274, + KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276, + KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E, + KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F, + KTX_COMPRESSED_RGB_FXT1_3DFX = 0x86B0, + KTX_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01, + KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00, + KTX_COMPRESSED_RGB_S3TC_DXT1 = 0x83F0, + KTX_COMPRESSED_RGBA = 0x84EE, + KTX_COMPRESSED_RGBA8_ETC2_EAC = 0x9278, + KTX_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C, + KTX_COMPRESSED_RGBA_FXT1_3DFX = 0x86B1, + KTX_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03, + KTX_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG = 0x9137, + KTX_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02, + KTX_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG = 0x9138, + KTX_COMPRESSED_RGBA_S3TC_DXT1 = 0x83F1, + KTX_COMPRESSED_RGBA_S3TC_DXT3 = 0x83F2, + KTX_COMPRESSED_RGBA_S3TC_DXT5 = 0x83F3, + KTX_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2 = 0x8C73, + KTX_COMPRESSED_SIGNED_LUMINANCE_LATC1 = 0x8C71, + KTX_COMPRESSED_SIGNED_R11_EAC = 0x9271, + KTX_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC, + KTX_COMPRESSED_SIGNED_RG11_EAC = 0x9273, + KTX_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE, + KTX_COMPRESSED_SLUMINANCE = 0x8C4A, + KTX_COMPRESSED_SLUMINANCE_ALPHA = 0x8C4B, + KTX_COMPRESSED_SRGB = 0x8C48, + KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279, + KTX_COMPRESSED_SRGB8_ETC2 = 0x9275, + KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277, + KTX_COMPRESSED_SRGB_ALPHA = 0x8C49, + KTX_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D, + KTX_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1 = 0x8A56, + KTX_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2 = 0x93F0, + KTX_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1 = 0x8A57, + KTX_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV2 = 0x93F1, + KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1 = 0x8C4D, + KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT3 = 0x8C4E, + KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT5 = 0x8C4F, + KTX_COMPRESSED_SRGB_PVRTC_2BPPV1 = 0x8A54, + KTX_COMPRESSED_SRGB_PVRTC_4BPPV1 = 0x8A55, + KTX_COMPRESSED_SRGB_S3TC_DXT1 = 0x8C4C, + KTX_ETC1_RGB8 = 0x8D64, + KTX_ETC1_SRGB8 = 0x88EE, +}; + /** * Constructs an empty texture. The default is to set up the texture as an * empty 2-d texture; follow up with one of the variants of setup_texture() if @@ -709,6 +934,23 @@ read_dds(istream &in, const string &filename, bool header_only) { return do_read_dds(cdata, in, filename, header_only); } +/** + * Reads the texture from a KTX file object. This is a Khronos-defined file + * format; it is similar in principle to a dds object, in that it is designed + * to contain the texture image in a form as similar as possible to its + * runtime image, and it can contain mipmaps, pre-compressed textures, and so + * on. + * + * As with read_dds, the filename is just for reference. + */ +bool Texture:: +read_ktx(istream &in, const string &filename, bool header_only) { + CDWriter cdata(_cycler, true); + cdata->inc_properties_modified(); + cdata->inc_image_modified(); + return do_read_ktx(cdata, in, filename, header_only); +} + /** * Loads a texture whose filename is derived by concatenating a suffix to the * filename of this texture. May return NULL, for example, if this texture @@ -1439,6 +1681,7 @@ write(ostream &out, int indent_level) const { case T_unsigned_int_24_8: case T_int: + case T_unsigned_int: out << " ints"; break; @@ -1862,6 +2105,8 @@ format_component_type(ComponentType ct) { return "short"; case T_half_float: return "half_float"; + case T_unsigned_int: + return "unsigned_int"; } return "**invalid**"; @@ -1888,6 +2133,8 @@ string_component_type(const string &str) { return T_short; } else if (cmp_nocase(str, "half_float") == 0) { return T_half_float; + } else if (cmp_nocase(str, "unsigned_int") == 0) { + return T_unsigned_int; } gobj_cat->error() @@ -1956,7 +2203,7 @@ format_format(Format format) { case F_r16: return "r16"; case F_r16i: - return "r16i"; + return "r16i"; case F_rg16: return "rg16"; case F_rgb16: @@ -2123,6 +2370,12 @@ format_compression_mode(CompressionMode cm) { return "pvr1_4bpp"; case CM_rgtc: return "rgtc"; + case CM_etc1: + return "etc1"; + case CM_etc2: + return "etc2"; + case CM_eac: + return "eac"; } return "**invalid**"; @@ -2158,6 +2411,12 @@ string_compression_mode(const string &str) { return CM_pvr1_4bpp; } else if (cmp_nocase_uh(str, "rgtc") == 0) { return CM_rgtc; + } else if (cmp_nocase_uh(str, "etc1") == 0) { + return CM_etc1; + } else if (cmp_nocase_uh(str, "etc2") == 0) { + return CM_etc2; + } else if (cmp_nocase_uh(str, "eac") == 0) { + return CM_eac; } gobj_cat->error() @@ -2272,7 +2531,8 @@ bool Texture:: is_unsigned(Texture::ComponentType ctype) { return (ctype == T_unsigned_byte || ctype == T_unsigned_short || - ctype == T_unsigned_int_24_8); + ctype == T_unsigned_int_24_8 || + ctype == T_unsigned_int); } /** @@ -2535,6 +2795,13 @@ do_read(CData *cdata, const Filename &fullpath, const Filename &alpha_fullpath, return do_read_dds_file(cdata, fullpath, header_only); } + if (is_ktx_filename(fullpath)) { + if (record != (BamCacheRecord *)NULL) { + record->add_dependent_file(fullpath); + } + return do_read_ktx_file(cdata, fullpath, header_only); + } + // If read_pages or read_mipmaps is specified, then z and n actually // indicate z_size and n_size, respectively--the numerical limits on which // to search for filenames. @@ -3820,6 +4087,770 @@ do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only) return true; } +/** + * Called internally when read() detects a KTX file. Assumes the lock is + * already held. + */ +bool Texture:: +do_read_ktx_file(CData *cdata, const Filename &fullpath, bool header_only) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + Filename filename = Filename::binary_filename(fullpath); + PT(VirtualFile) file = vfs->get_file(filename); + if (file == (VirtualFile *)NULL) { + // No such file. + gobj_cat.error() + << "Could not find " << fullpath << "\n"; + return false; + } + + if (gobj_cat.is_debug()) { + gobj_cat.debug() + << "Reading KTX file " << filename << "\n"; + } + + istream *in = file->open_read_file(true); + bool success = do_read_ktx(cdata, *in, fullpath, header_only); + vfs->close_read_file(in); + + if (!has_name()) { + set_name(fullpath.get_basename_wo_extension()); + } + + cdata->_fullpath = fullpath; + cdata->_alpha_fullpath = Filename(); + cdata->_keep_ram_image = false; + + return success; +} + +/** + * + */ +bool Texture:: +do_read_ktx(CData *cdata, istream &in, const string &filename, bool header_only) { + StreamReader ktx(in); + + if (ktx.extract_bytes(12) != "\xABKTX 11\xBB\r\n\x1A\n") { + gobj_cat.error() + << filename << " is not a KTX file.\n"; + return false; + } + + // See: https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + uint32_t gl_type, type_size, gl_format, internal_format, gl_base_format, + width, height, depth, num_array_elements, num_faces, num_mipmap_levels, + kvdata_size; + + bool big_endian; + if (ktx.get_uint32() == 0x04030201) { + big_endian = false; + gl_type = ktx.get_uint32(); + type_size = ktx.get_uint32(); + gl_format = ktx.get_uint32(); + internal_format = ktx.get_uint32(); + gl_base_format = ktx.get_uint32(); + width = ktx.get_uint32(); + height = ktx.get_uint32(); + depth = ktx.get_uint32(); + num_array_elements = ktx.get_uint32(); + num_faces = ktx.get_uint32(); + num_mipmap_levels = ktx.get_uint32(); + kvdata_size = ktx.get_uint32(); + } else { + big_endian = true; + gl_type = ktx.get_be_uint32(); + type_size = ktx.get_be_uint32(); + gl_format = ktx.get_be_uint32(); + internal_format = ktx.get_be_uint32(); + gl_base_format = ktx.get_be_uint32(); + width = ktx.get_be_uint32(); + height = ktx.get_be_uint32(); + depth = ktx.get_be_uint32(); + num_array_elements = ktx.get_be_uint32(); + num_faces = ktx.get_be_uint32(); + num_mipmap_levels = ktx.get_be_uint32(); + kvdata_size = ktx.get_be_uint32(); + } + + // Skip metadata section. + ktx.skip_bytes(kvdata_size); + + ComponentType type; + CompressionMode compression; + Format format; + bool swap_bgr = false; + + if (gl_type == 0 || gl_format == 0) { + // Compressed texture. + if (gl_type > 0 || gl_format > 0) { + gobj_cat.error() + << "Compressed textures must have both type and format set to 0.\n"; + return false; + } + type = T_unsigned_byte; + compression = CM_on; + + KTXFormat base_format; + switch ((KTXCompressedFormat)internal_format) { + case KTX_COMPRESSED_RED: + format = F_red; + base_format = KTX_RED; + break; + case KTX_COMPRESSED_RG: + format = F_rg; + base_format = KTX_RG; + break; + case KTX_COMPRESSED_RGB: + format = F_rgb; + base_format = KTX_RGB; + break; + case KTX_COMPRESSED_RGBA: + format = F_rgba; + base_format = KTX_RGBA; + break; + case KTX_COMPRESSED_SRGB: + format = F_srgb; + base_format = KTX_SRGB; + break; + case KTX_COMPRESSED_SRGB_ALPHA: + format = F_srgb_alpha; + base_format = KTX_SRGB_ALPHA; + break; + case KTX_COMPRESSED_RGB_FXT1_3DFX: + format = F_rgb; + base_format = KTX_RGB; + compression = CM_fxt1; + break; + case KTX_COMPRESSED_RGBA_FXT1_3DFX: + format = F_rgba; + base_format = KTX_RGBA; + compression = CM_fxt1; + break; + case KTX_COMPRESSED_RGB_S3TC_DXT1: + format = F_rgb; + base_format = KTX_RGB; + compression = CM_dxt1; + break; + case KTX_COMPRESSED_RGBA_S3TC_DXT1: + format = F_rgbm; + base_format = KTX_RGB; + compression = CM_dxt1; + break; + case KTX_COMPRESSED_RGBA_S3TC_DXT3: + format = F_rgba; + base_format = KTX_RGBA; + compression = CM_dxt3; + break; + case KTX_COMPRESSED_RGBA_S3TC_DXT5: + format = F_rgba; + base_format = KTX_RGBA; + compression = CM_dxt5; + break; + case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1: + format = F_srgb_alpha; + base_format = KTX_SRGB_ALPHA; + compression = CM_dxt1; + break; + case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT3: + format = F_srgb_alpha; + base_format = KTX_SRGB_ALPHA; + compression = CM_dxt3; + break; + case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT5: + format = F_srgb_alpha; + base_format = KTX_SRGB_ALPHA; + compression = CM_dxt5; + break; + case KTX_COMPRESSED_SRGB_S3TC_DXT1: + format = F_srgb; + base_format = KTX_SRGB; + compression = CM_dxt1; + break; + case KTX_COMPRESSED_RED_RGTC1: + case KTX_COMPRESSED_SIGNED_RED_RGTC1: + format = F_red; + base_format = KTX_RED; + compression = CM_rgtc; + break; + case KTX_COMPRESSED_RG_RGTC2: + case KTX_COMPRESSED_SIGNED_RG_RGTC2: + format = F_rg; + base_format = KTX_RG; + compression = CM_rgtc; + break; + case KTX_ETC1_RGB8: + format = F_rgb; + base_format = KTX_RGB; + compression = CM_etc1; + break; + case KTX_ETC1_SRGB8: + format = F_srgb; + base_format = KTX_SRGB; + compression = CM_etc1; + break; + case KTX_COMPRESSED_RGB8_ETC2: + format = F_rgb; + base_format = KTX_RGB; + compression = CM_etc2; + break; + case KTX_COMPRESSED_SRGB8_ETC2: + format = F_srgb; + base_format = KTX_SRGB; + compression = CM_etc2; + break; + case KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + format = F_rgbm; + base_format = KTX_RGBA; + compression = CM_etc2; + break; + case KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + format = F_rgbm; + base_format = KTX_SRGB8_ALPHA8; + compression = CM_etc2; + break; + case KTX_COMPRESSED_RGBA8_ETC2_EAC: + format = F_rgba; + base_format = KTX_RGBA; + compression = CM_etc2; + break; + case KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + format = F_srgb_alpha; + base_format = KTX_SRGB8_ALPHA8; + compression = CM_etc2; + break; + case KTX_COMPRESSED_R11_EAC: + case KTX_COMPRESSED_SIGNED_R11_EAC: + format = F_red; + base_format = KTX_RED; + compression = CM_eac; + break; + case KTX_COMPRESSED_RG11_EAC: + case KTX_COMPRESSED_SIGNED_RG11_EAC: + format = F_rg; + base_format = KTX_RG; + compression = CM_eac; + break; + case KTX_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1: + format = F_srgb_alpha; + base_format = KTX_SRGB_ALPHA; + compression = CM_pvr1_2bpp; + break; + case KTX_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1: + format = F_srgb_alpha; + base_format = KTX_SRGB_ALPHA; + compression = CM_pvr1_4bpp; + break; + case KTX_COMPRESSED_RGBA_BPTC_UNORM: + case KTX_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT: + case KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: + default: + gobj_cat.error() + << filename << " has unsupported compressed internal format " << internal_format << "\n"; + return false; + } + + if (base_format != gl_base_format) { + gobj_cat.error() + << filename << " has internal format that is incompatible with base " + "format (0x" << hex << gl_base_format << ", expected 0x" + << base_format << dec << ")\n"; + return false; + } + + } else { + // Uncompressed texture. + compression = CM_off; + switch ((KTXType)gl_type) { + case KTX_BYTE: + type = T_byte; + break; + case KTX_UNSIGNED_BYTE: + type = T_unsigned_byte; + break; + case KTX_SHORT: + type = T_short; + break; + case KTX_UNSIGNED_SHORT: + type = T_unsigned_short; + break; + case KTX_INT: + type = T_int; + break; + case KTX_UNSIGNED_INT: + type = T_unsigned_int; + break; + case KTX_FLOAT: + type = T_float; + break; + case KTX_HALF_FLOAT: + type = T_half_float; + break; + case KTX_UNSIGNED_INT_24_8: + type = T_unsigned_int_24_8; + break; + default: + gobj_cat.error() + << filename << " has unsupported component type " << gl_type << "\n"; + return false; + } + + if (gl_format != gl_base_format) { + gobj_cat.error() + << filename << " has mismatched formats: " << gl_format << " != " + << gl_base_format << "\n"; + } + + switch (gl_format) { + case KTX_DEPTH_COMPONENT: + switch (internal_format) { + case KTX_DEPTH_COMPONENT: + format = F_depth_component; + break; + case KTX_DEPTH_COMPONENT16: + format = F_depth_component16; + break; + case KTX_DEPTH_COMPONENT24: + format = F_depth_component24; + break; + case KTX_DEPTH_COMPONENT32: + case KTX_DEPTH_COMPONENT32F: + format = F_depth_component32; + break; + default: + format = F_depth_component; + gobj_cat.warning() + << filename << " has unsupported depth component format " << internal_format << "\n"; + } + break; + + case KTX_DEPTH_STENCIL: + format = F_depth_stencil; + if (internal_format != KTX_DEPTH_STENCIL && + internal_format != KTX_DEPTH24_STENCIL8) { + gobj_cat.warning() + << filename << " has unsupported depth stencil format " << internal_format << "\n"; + } + break; + + case KTX_RED: + switch (internal_format) { + case KTX_RED: + case KTX_RED_SNORM: + case KTX_R8: + case KTX_R8_SNORM: + format = F_red; + break; + case KTX_R16: + case KTX_R16_SNORM: + case KTX_R16F: + format = F_r16; + break; + case KTX_R32F: + format = F_r32; + break; + default: + format = F_red; + gobj_cat.warning() + << filename << " has unsupported red format " << internal_format << "\n"; + } + break; + + case KTX_RED_INTEGER: + switch (internal_format) { + case KTX_R8I: + case KTX_R8UI: + format = F_r8i; + break; + case KTX_R16I: + case KTX_R16UI: + format = F_r16i; + break; + case KTX_R32I: + case KTX_R32UI: + format = F_r32i; + break; + default: + gobj_cat.error() + << filename << " has unsupported red integer format " << internal_format << "\n"; + return false; + } + break; + + case KTX_GREEN: + format = F_green; + if (internal_format != KTX_GREEN) { + gobj_cat.warning() + << filename << " has unsupported green format " << internal_format << "\n"; + } + break; + + case KTX_BLUE: + format = F_blue; + if (internal_format != KTX_BLUE) { + gobj_cat.warning() + << filename << " has unsupported blue format " << internal_format << "\n"; + } + break; + + case KTX_RG: + switch (internal_format) { + case KTX_RG: + case KTX_RG_SNORM: + case KTX_RG8: + case KTX_RG8_SNORM: + format = F_rg; + break; + case KTX_RG16: + case KTX_RG16_SNORM: + case KTX_RG16F: + format = F_rg16; + break; + case KTX_RG32F: + format = F_rg32; + break; + default: + format = F_rg; + gobj_cat.warning() + << filename << " has unsupported RG format " << internal_format << "\n"; + } + break; + + case KTX_RG_INTEGER: + switch (internal_format) { + case KTX_RG8I: + case KTX_RG8UI: + format = F_rg8i; + break; + case KTX_RG16I: + case KTX_RG16UI: + case KTX_RG32I: + case KTX_RG32UI: + default: + gobj_cat.error() + << filename << " has unsupported RG integer format " << internal_format << "\n"; + return false; + } + break; + + case KTX_RGB: + swap_bgr = true; + case KTX_BGR: + switch (internal_format) { + case KTX_RGB: + case KTX_RGB_SNORM: + format = F_rgb; + break; + case KTX_RGB5: + format = F_rgb5; + break; + case KTX_RGB12: + format = F_rgb12; + break; + case KTX_R3_G3_B2: + format = F_rgb332; + break; + case KTX_RGB9_E5: + format = F_rgb9_e5; + break; + case KTX_R11F_G11F_B10F: + format = F_r11_g11_b10; + break; + case KTX_RGB8: + case KTX_RGB8_SNORM: + format = F_rgb8; + break; + case KTX_RGB16: + case KTX_RGB16_SNORM: + case KTX_RGB16F: + format = F_rgb16; + break; + case KTX_RGB32F: + format = F_rgb32; + break; + case KTX_SRGB: + case KTX_SRGB8: + format = F_srgb; + break; + default: + format = F_rgb; + gobj_cat.warning() + << filename << " has unsupported RGB format " << internal_format << "\n"; + } + break; + + case KTX_RGB_INTEGER: + swap_bgr = true; + case KTX_BGR_INTEGER: + switch (internal_format) { + case KTX_RGB8I: + case KTX_RGB8UI: + format = F_rgb8i; + break; + case KTX_RGB16I: + case KTX_RGB16UI: + case KTX_RGB32I: + case KTX_RGB32UI: + default: + gobj_cat.error() + << filename << " has unsupported RGB integer format " << internal_format << "\n"; + return false; + } + break; + + case KTX_RGBA: + swap_bgr = true; + case KTX_BGRA: + switch (internal_format) { + case KTX_RGBA: + case KTX_RGBA_SNORM: + format = F_rgba; + break; + case KTX_RGBA4: + format = F_rgba4; + break; + case KTX_RGB5_A1: + format = F_rgba5; + break; + case KTX_RGBA12: + format = F_rgba12; + break; + case KTX_RGB10_A2: + format = F_rgb10_a2; + break; + case KTX_RGBA8: + case KTX_RGBA8_SNORM: + format = F_rgba8; + break; + case KTX_RGBA16: + case KTX_RGBA16_SNORM: + case KTX_RGBA16F: + format = F_rgba16; + break; + case KTX_RGBA32F: + format = F_rgba32; + break; + case KTX_SRGB_ALPHA: + case KTX_SRGB8_ALPHA8: + format = F_srgb_alpha; + break; + default: + format = F_rgba; + gobj_cat.warning() + << filename << " has unsupported RGBA format " << internal_format << "\n"; + } + break; + break; + + case KTX_RGBA_INTEGER: + swap_bgr = true; + case KTX_BGRA_INTEGER: + switch (internal_format) { + case KTX_RGBA8I: + case KTX_RGBA8UI: + format = F_rgba8i; + break; + case KTX_RGBA16I: + case KTX_RGBA16UI: + case KTX_RGBA32I: + case KTX_RGBA32UI: + default: + gobj_cat.error() + << filename << " has unsupported RGBA integer format " << internal_format << "\n"; + return false; + } + break; + + case KTX_LUMINANCE: + format = F_luminance; + break; + + case KTX_LUMINANCE_ALPHA: + format = F_luminance_alpha; + break; + + case KTX_ALPHA: + format = F_alpha; + break; + + case KTX_STENCIL_INDEX: + default: + gobj_cat.error() + << filename << " has unsupported format " << gl_format << "\n"; + return false; + } + } + + TextureType texture_type; + if (depth > 0) { + texture_type = TT_3d_texture; + + } else if (num_faces > 1) { + if (num_faces != 6) { + gobj_cat.error() + << filename << " has " << num_faces << " cube map faces, expected 6\n"; + return false; + } + if (width != height) { + gobj_cat.error() + << filename << " is cube map, but does not have square dimensions\n"; + return false; + } + if (num_array_elements > 0) { + depth = num_array_elements * 6; + texture_type = TT_cube_map_array; + } else { + depth = 6; + texture_type = TT_cube_map; + } + + } else if (height > 0) { + if (num_array_elements > 0) { + depth = num_array_elements; + texture_type = TT_2d_texture_array; + } else { + depth = 1; + texture_type = TT_2d_texture; + } + + } else if (width > 0) { + depth = 1; + if (num_array_elements > 0) { + height = num_array_elements; + texture_type = TT_1d_texture_array; + } else { + height = 1; + texture_type = TT_1d_texture; + } + + } else { + gobj_cat.error() + << filename << " has zero size\n"; + return false; + } + + do_setup_texture(cdata, texture_type, width, height, depth, type, format); + + cdata->_orig_file_x_size = cdata->_x_size; + cdata->_orig_file_y_size = cdata->_y_size; + cdata->_compression = compression; + cdata->_ram_image_compression = compression; + + if (!header_only) { + bool generate_mipmaps = false; + if (num_mipmap_levels == 0) { + generate_mipmaps = true; + num_mipmap_levels = 1; + } + + for (uint32_t n = 0; n < num_mipmap_levels; ++n) { + uint32_t image_size; + if (big_endian) { + image_size = ktx.get_be_uint32(); + } else { + image_size = ktx.get_uint32(); + } + PTA_uchar image; + + if (compression == CM_off) { + uint32_t row_size = do_get_expected_mipmap_x_size(cdata, (int)n) * cdata->_num_components * cdata->_component_width; + uint32_t num_rows = do_get_expected_mipmap_y_size(cdata, (int)n) * do_get_expected_mipmap_z_size(cdata, (int)n); + uint32_t row_padded = (row_size + 3) & ~3; + + if (image_size == row_size * num_rows) { + if (row_padded != row_size) { + // Someone tightly packed the image. This is invalid, but because + // we like it tightly packed too, we'll read it anyway. + gobj_cat.warning() + << filename << " does not have proper row padding for mipmap " + "level " << n << "\n"; + } + image = PTA_uchar::empty_array(image_size); + ktx.extract_bytes(image.p(), image_size); + + } else if (image_size != row_padded * num_rows) { + gobj_cat.error() + << filename << " has invalid image size " << image_size + << " for mipmap level " << n << " (expected " + << row_padded * num_rows << ")\n"; + return false; + + } else { + // Read it row by row. + image = PTA_uchar::empty_array(row_size * num_rows); + uint32_t skip = row_padded - row_size; + unsigned char *p = image.p(); + for (uint32_t row = 0; row < num_rows; ++row) { + ktx.extract_bytes(p, row_size); + ktx.skip_bytes(skip); + p += row_size; + } + } + + // Swap red and blue channels if necessary to match Panda conventions. + if (swap_bgr) { + unsigned char *begin = image.p(); + const unsigned char *end = image.p() + image.size(); + size_t skip = cdata->_num_components; + nassertr(skip == 3 || skip == 4, false); + + switch (cdata->_component_width) { + case 1: + for (unsigned char *p = begin; p < end; p += skip) { + swap(p[0], p[2]); + } + break; + case 2: + for (short *p = (short *)begin; p < (short *)end; p += skip) { + swap(p[0], p[2]); + } + break; + case 4: + for (int *p = (int *)begin; p < (int *)end; p += skip) { + swap(p[0], p[2]); + } + break; + default: + nassertr(false, false); + break; + } + } + + do_set_ram_mipmap_image(cdata, (int)n, MOVE(image), + row_size * do_get_expected_mipmap_y_size(cdata, (int)n)); + + } else { + // Compressed image. We'll trust that the file has the right size. + image = PTA_uchar::empty_array(image_size); + ktx.extract_bytes(image.p(), image_size); + do_set_ram_mipmap_image(cdata, (int)n, MOVE(image), image_size / depth); + } + + ktx.skip_bytes(3 - ((image_size + 3) & 3)); + } + + cdata->_has_read_pages = true; + cdata->_has_read_mipmaps = true; + cdata->_num_mipmap_levels_read = cdata->_ram_images.size(); + + if (generate_mipmaps) { + do_generate_ram_mipmap_images(cdata, false); + } + } + + if (in.fail() || in.eof()) { + gobj_cat.error() + << filename << ": truncated KTX file.\n"; + return false; + } + + cdata->_loaded_from_image = true; + cdata->_loaded_from_txo = true; + + return true; +} + /** * Internal method to write a series of pages and/or mipmap levels to disk * files. @@ -4607,6 +5638,27 @@ do_get_clear_data(const CData *cdata, unsigned char *into) const { } break; } + + case T_unsigned_int: + { + // Note: there are no 32-bit UNORM textures. Therefore, we don't do any + // normalization here, either. + switch (cdata->_num_components) { + case 2: + ((unsigned int *)into)[1] = (unsigned int)cdata->_clear_color[1]; + case 1: + ((unsigned int *)into)[0] = (unsigned int)cdata->_clear_color[0]; + break; + case 4: + ((unsigned int *)into)[3] = (unsigned int)cdata->_clear_color[3]; + case 3: // BGR <-> RGB + ((unsigned int *)into)[0] = (unsigned int)cdata->_clear_color[2]; + ((unsigned int *)into)[1] = (unsigned int)cdata->_clear_color[1]; + ((unsigned int *)into)[2] = (unsigned int)cdata->_clear_color[0]; + break; + } + break; + } } return cdata->_num_components * cdata->_component_width; @@ -4692,18 +5744,24 @@ do_compress_ram_image(CData *cdata, Texture::CompressionMode compression, case Texture::F_rgb10_a2: if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt1)) { compression = CM_dxt1; - } else if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt3)) { + } else if (gsg->get_supports_compressed_texture_format(CM_dxt3)) { compression = CM_dxt3; - } else if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt5)) { + } else if (gsg->get_supports_compressed_texture_format(CM_dxt5)) { compression = CM_dxt5; + } else if (gsg->get_supports_compressed_texture_format(CM_etc2)) { + compression = CM_etc2; + } else if (gsg->get_supports_compressed_texture_format(CM_etc1)) { + compression = CM_etc1; } break; case Texture::F_rgba4: if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt3)) { compression = CM_dxt3; - } else if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt5)) { + } else if (gsg->get_supports_compressed_texture_format(CM_dxt5)) { compression = CM_dxt5; + } else if (gsg->get_supports_compressed_texture_format(CM_etc2)) { + compression = CM_etc2; } break; @@ -4714,6 +5772,8 @@ do_compress_ram_image(CData *cdata, Texture::CompressionMode compression, case Texture::F_rgba32: if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt5)) { compression = CM_dxt5; + } else if (gsg->get_supports_compressed_texture_format(CM_etc2)) { + compression = CM_etc2; } break; @@ -4721,6 +5781,8 @@ do_compress_ram_image(CData *cdata, Texture::CompressionMode compression, case Texture::F_rg: if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_rgtc)) { compression = CM_rgtc; + } else if (gsg->get_supports_compressed_texture_format(CM_eac)) { + compression = CM_eac; } break; @@ -5822,14 +6884,9 @@ do_set_component_type(CData *cdata, Texture::ComponentType component_type) { break; case T_float: - cdata->_component_width = 4; - break; - case T_unsigned_int_24_8: - cdata->_component_width = 4; - break; - case T_int: + case T_unsigned_int: cdata->_component_width = 4; break; } @@ -6472,7 +7529,12 @@ do_generate_ram_mipmap_images(CData *cdata, bool allow_recompress) { // Now try to get the uncompressed source image. do_get_uncompressed_ram_image(cdata); - nassertv(cdata->_ram_image_compression == CM_off); + if (cdata->_ram_image_compression != CM_off) { + gobj_cat.error() + << "Cannot generate mipmap levels for image with compression " + << cdata->_ram_image_compression << "\n"; + return; + } } do_clear_ram_mipmap_images(cdata); diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index 010f847b6a..1f125fd7ce 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -79,6 +79,7 @@ PUBLISHED: TT_cube_map, TT_buffer_texture, TT_cube_map_array, + TT_1d_texture_array, }; enum ComponentType { @@ -90,6 +91,7 @@ PUBLISHED: T_byte, T_short, T_half_float, + T_unsigned_int, }; enum Format { @@ -206,6 +208,9 @@ PUBLISHED: CM_pvr1_2bpp, CM_pvr1_4bpp, CM_rgtc, // BC4/BC5: 1 or 2 channels, individually compressed. + CM_etc1, + CM_etc2, + CM_eac, // EAC: 1 or 2 channels. }; enum QualityLevel { @@ -284,6 +289,7 @@ PUBLISHED: BLOCKING static PT(Texture) make_from_txo(istream &in, const string &filename = ""); BLOCKING bool write_txo(ostream &out, const string &filename = "") const; BLOCKING bool read_dds(istream &in, const string &filename = "", bool header_only = false); + BLOCKING bool read_ktx(istream &in, const string &filename = "", bool header_only = false); BLOCKING INLINE bool load(const PNMImage &pnmimage, const LoaderOptions &options = LoaderOptions()); BLOCKING INLINE bool load(const PNMImage &pnmimage, int z, int n, const LoaderOptions &options = LoaderOptions()); @@ -657,6 +663,8 @@ protected: bool do_read_txo(CData *cdata, istream &in, const string &filename); bool do_read_dds_file(CData *cdata, const Filename &fullpath, bool header_only); bool do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only); + bool do_read_ktx_file(CData *cdata, const Filename &fullpath, bool header_only); + bool do_read_ktx(CData *cdata, istream &in, const string &filename, bool header_only); bool do_write(CData *cdata, const Filename &fullpath, int z, int n, bool write_pages, bool write_mipmaps); @@ -840,6 +848,7 @@ private: INLINE static bool is_txo_filename(const Filename &fullpath); INLINE static bool is_dds_filename(const Filename &fullpath); + INLINE static bool is_ktx_filename(const Filename &fullpath); void do_filter_2d_mipmap_pages(const CData *cdata, RamImage &to, const RamImage &from, diff --git a/panda/src/gobj/texturePool.cxx b/panda/src/gobj/texturePool.cxx index cf73364cde..5c05040d83 100644 --- a/panda/src/gobj/texturePool.cxx +++ b/panda/src/gobj/texturePool.cxx @@ -90,7 +90,7 @@ get_texture_type(const string &extension) const { // Check the PNM type registry. PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr(); PNMFileType *type = pnm_reg->get_type_from_extension(c); - if (type != (PNMFileType *)NULL) { + if (type != (PNMFileType *)NULL || c == "txo" || c == "dds" || c == "ktx") { // This is a known image type; create an ordinary Texture. ((TexturePool *)this)->_type_registry[c] = Texture::make_texture; return Texture::make_texture; @@ -109,6 +109,11 @@ void TexturePool:: write_texture_types(ostream &out, int indent_level) const { MutexHolder holder(_lock); + // These are supported out of the box. + indent(out, indent_level) << "Texture Object .txo\n"; + indent(out, indent_level) << "DirectDraw Surface .dds\n"; + indent(out, indent_level) << "Khronos Texture .ktx\n"; + PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr(); pnm_reg->write(out, indent_level);