Magnum::Trade::BasisImporter class new in 2019.10

Basis Universal importer plugin.

Imports Basis Universal compressed images (*.basis or *.ktx2) by parsing and transcoding files into an explicitly specified GPU format (see Target format). You can use BasisImageConverter to transcode images into this format.

This plugin provides BasisImporterEacR, BasisImporterEacRG, BasisImporterEtc1RGB, BasisImporterEtc2RGBA, BasisImporterBc1RGB, BasisImporterBc3RGBA, BasisImporterBc4R, BasisImporterBc5RG, BasisImporterBc7RGBA, BasisImporterPvrtc1RGB4bpp, BasisImporterPvrtc1RGBA4bpp, BasisImporterAstc4x4RGBA, BasisImporterRGBA8.

Usage

This plugin depends on the Trade and Basis Universal libraries and is built if MAGNUM_WITH_BASISIMPORTER is enabled when building Magnum Plugins. To use as a dynamic plugin, load "BasisImporter" via Corrade::PluginManager::Manager. Current version of the plugin is tested against the v1_15_update2 tag, but could possibly compile against newer versions as well.

Additionally, if you're using Magnum as a CMake subproject, bundle the magnum-plugins, zstd and basis-universal repositories and do the following. Basis uses Zstd for KTX2 images, instead of bundling you can depend on an externally-installed zstd package, in which case omit the first part of the setup below. If Zstd isn't bundled and isn't found externally, Basis will be compiled without Zstd support. See Reducing binary size below for additional options to control what gets built.

set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
# Create a static library so the plugin is self-contained
set(ZSTD_BUILD_SHARED OFF CACHE BOOL "" FORCE)
set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE)
# Basis doesn't use any multithreading in zstd, this prevents a need to link to
# pthread on Linux
set(ZSTD_MULTITHREAD_SUPPORT OFF CACHE BOOL "" FORCE)
# Don't build Zstd tests if enable_testing() was called in parent project
set(ZSTD_BUILD_TESTS OFF CACHE BOOL "" FORCE)
add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL)

set(BASIS_UNIVERSAL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/basis-universal)
set(MAGNUM_WITH_BASISIMPORTER ON CACHE BOOL "" FORCE)
add_subdirectory(magnum-plugins EXCLUDE_FROM_ALL)

# So the dynamically loaded plugin gets built implicitly
add_dependencies(your-app MagnumPlugins::BasisImporter)

To use as a static plugin or as a dependency of another plugin with CMake, put FindMagnumPlugins.cmake and FindBasisUniversal.cmake into your modules/ directory, request the BasisImporter component of the MagnumPlugins package and link to the MagnumPlugins::BasisImporter target:

find_package(MagnumPlugins REQUIRED BasisImporter)

# ...
target_link_libraries(your-app PRIVATE MagnumPlugins::BasisImporter)

See Downloading and building plugins, Plugin usage with CMake, Loading and using plugins and File format support for more information.

Behavior and limitations

The importer recognizes ImporterFlag::Verbose, printing additional info when the flag is enabled. ImporterFlag::Quiet is recognized as well and causes all import warnings to be suppressed.

Image types

You can import all image types supported by basisu: (layered) 2D images, (layered) cube maps, 3D images and videos. They can in turn all have multiple mip levels. The images are annotated with ImageFlag3D::Array and ImageFlag3D::CubeMap as appropriate. Furthermore, for backward compatibility, if MAGNUM_BUILD_DEPRECATED is enabled, the image type can also be determined from texture() and TextureData::type().

For layered 2D images and (layered) cube maps, the array layers and faces are exposed as an additional image dimension. image3D() will return an ImageData3D with n z-slices, or 6*n z-slices for cube maps.

All 3D images will be imported as 2D array textures with as many layers as depth slices. This unifies the behaviour with Basis compressed KTX2 files that don't support 3D images in the first place, and avoids confusing behaviour with mip levels which are always 2-dimensional in Basis compressed images.

Video files will be imported as multiple 2D images with the same size and level count. Due to the way video is encoded by Basis Universal, seeking to arbitrary frames is not allowed. If you call image2D() with non-sequential frame indices and that frame is not an I-frame, it will print an error and fail. Restarting from frame 0 is always allowed.

Multilevel images

Files with multiple mip levels are imported with the largest level first, with the size of each following level divided by 2, rounded down. Mip chains can be incomplete, ie. they don't have to extend all the way down to a level of size 1x1.

Because mip levels in .basis files are always 2-dimensional, they wouldn't halve correctly in the z-dimension for 3D images. If a 3D image with mip levels is detected, it gets imported as a layered 2D image instead, along with a warning being printed.

Cube maps

Cube map faces are imported in the order +X, -X, +Y, -Y, +Z, -Z as seen from a left-handed coordinate system (+X is right, +Y is up, +Z is forward). Layered cube maps are stored as multiple sets of faces, ie. all faces +X through -Z for the first layer, then all faces of the second layer, etc.

KTX2 files

Basis Universal supports only the Basis-encoded subset of the KTX2 format. It treats non-Basis-encoded KTX2 files the same way as broken KTX2 files, and so the plugin cannot robustly proxy the loading to KtxImporter in that case. Instead, if you're dealing with generic KTX2 files, you're encouraged to use KtxImporter directly — it will then delegate to BasisImporter for Basis-encoded files.

Plugin-specific configuration

Basis allows configuration of the format of loaded compressed data. The full form of the configuration is shown below:

[configuration]
# Enable to assume that the file is written in a Y up orientation. If empty,
# the orientation is read from metadata in the file header, and if not
# present it's assumed to be Y down. Images are then flipped on import to
# have Y up, or a warning is printed in case it's not possible. Basis doesn't
# support 3D textures, only 2D arrays and cubemaps, so Z orientation never
# needs to be adjusted.
assumeYUp=

# No format is specified by default and you have to choose one either by
# changing this value or by loading the plugin under an alias. Should be one
# of Etc1RGB, Etc2RGBA, EacR, EacRG, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGBA,
# PvrtcRGB4bpp, PvrtcRGBA4bpp, Astc4x4RGBA or RGBA8. If not set, falls back
# to RGBA8 with a warning.
format=

See Editing plugin-specific configuration for more information and an example showing how to edit the configuration values.

Target format

Basis is a compressed format that is transcoded into a compressed GPU format. With BasisImporter, this format can be chosen in different ways:

/* Choose ETC2 target format */
Containers::Pointer<Trade::AbstractImporter> importerEtc2 =
    manager.instantiate("BasisImporterEtc2");

/* Choose BC5 target format */
Containers::Pointer<Trade::AbstractImporter> importerBc5 =
    manager.instantiate("BasisImporterBc5");

The list of valid suffixes is equivalent to enum value names in TargetFormat. If you want to be able to change the target format dynamically, you may want to set the format configuration of the plugin, as shown below. If you instantiate this class directly without a plugin manager, you may also use setTargetFormat().

/* Instantiate the plugin under its default name. At this point, the plugin
   would decompress to full RGBA8, which is usually not what you want. */
Containers::Pointer<Trade::AbstractImporter> importer =
    manager.instantiate("BasisImporter");
importer->openFile("mytexture.basis");

/* Transcode the image to BC5 */
importer->configuration().setValue("format", "Bc5");
image = importer->image2D(0);
// ...

/* Transcode the same image, but to ETC2 now */
importer->configuration().setValue("format", "Etc2");
image = importer->image2D(0);
// ...

There are many options and you should generally be striving for the highest-quality format available on a given platform. A detailed description of the choices can be found in the Basis Universal Wiki. As an example, the following code is a decision making used by magnum-player based on availability of corresponding OpenGL, OpenGL ES and WebGL extensions, in its full ugly glory:

if(PluginManager::PluginMetadata* metadata = manager.metadata("BasisImporter")) {
    GL::Context& context = GL::Context::current();
    using namespace GL::Extensions;
    #ifdef MAGNUM_TARGET_WEBGL
    if(context.isExtensionSupported<WEBGL::compressed_texture_astc>())
    #else
    if(context.isExtensionSupported<KHR::texture_compression_astc_ldr>())
    #endif
    {
        metadata->configuration().setValue("format", "Astc4x4RGBA");
    }
    #ifdef MAGNUM_TARGET_GLES
    else if(context.isExtensionSupported<EXT::texture_compression_bptc>())
    #else
    else if(context.isExtensionSupported<ARB::texture_compression_bptc>())
    #endif
    {
        metadata->configuration().setValue("format", "Bc7RGBA");
    }
    #ifdef MAGNUM_TARGET_WEBGL
    else if(context.isExtensionSupported<WEBGL::compressed_texture_s3tc>())
    #elif defined(MAGNUM_TARGET_GLES)
    else if(context.isExtensionSupported<EXT::texture_compression_s3tc>() ||
            context.isExtensionSupported<ANGLE::texture_compression_dxt5>())
    #else
    else if(context.isExtensionSupported<EXT::texture_compression_s3tc>())
    #endif
    {
        metadata->configuration().setValue("format", "Bc3RGBA");
    } else
    /* ES3 (but not WebGL 2) has ETC always, so none of these ifs is there */
    #ifdef MAGNUM_TARGET_WEBGL
    if(context.isExtensionSupported<WEBGL::compressed_texture_etc>())
    #elif defined(MAGNUM_TARGET_GLES2)
    if(context.isExtensionSupported<ANGLE::compressed_texture_etc>())
    #elif !defined(MAGNUM_TARGET_GLES)
    if(context.isExtensionSupported<ARB::ES3_compatibility>())
    #endif
    {
        metadata->configuration().setValue("format", "Etc2RGBA");
    }
    /* On ES2 or WebGL fall back to PVRTC if ETC2 is not available */
    #if defined(MAGNUM_TARGET_GLES2) || defined(MAGNUM_TARGET_WEBGL)
    #ifdef MAGNUM_TARGET_WEBGL
    else if(context.isExtensionSupported<WEBGL::compressed_texture_pvrtc>())
    #else
    else if(context.isExtensionSupported<IMG::texture_compression_pvrtc>())
    #endif
    {
        metadata->configuration().setValue("format", "PvrtcRGBA4bpp");
    }
    #endif
    /* And then, for everything except ES3 (but not WebGL 2) which already
       stopped at ETC, fall back to uncompressed */
    #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) || defined(MAGNUM_TARGET_WEBGL)
    else {
        /* Fall back to uncompressed if nothing else is supported */
        metadata->configuration().setValue("format", "RGBA8");
    }
    #endif
}

Reducing binary size

To reduce the binary size of the transcoder, Basis Universal supports a set of preprocessor defines to turn off unneeded features. The Basis Universal Wiki lists macros to disable specific target formats as well as KTX2 support including the Zstd dependency. If you're building it from source with BASIS_UNIVERSAL_DIR set, add the desired defines before adding magnum-plugins as a subfolder:

add_definitions(
    -DBASISD_SUPPORT_BC7=0
    -DBASISD_SUPPORT_KTX2=0)

# ...
add_subdirectory(magnum-plugins EXCLUDE_FROM_ALL)

Base classes

class AbstractImporter
Base for importer plugins.

Public types

enum class TargetFormat: UnsignedInt { Etc1RGB = 0, Etc2RGBA = 1, Bc1RGB = 2, Bc3RGBA = 3, Bc4R = 4, Bc5RG = 5, Bc7RGBA = 6, PvrtcRGB4bpp = 8, PvrtcRGBA4bpp = 9, Astc4x4RGBA = 10, RGBA8 = 13, EacR = 20, EacRG = 21 }
Type to transcode to.

Public static functions

static void initialize()
Initialize Basis transcoder.

Constructors, destructors, conversion operators

BasisImporter() explicit
Default constructor.
BasisImporter(PluginManager::AbstractManager& manager, const Containers::StringView& plugin) explicit
Plugin manager constructor.

Public functions

auto targetFormat() const -> TargetFormat
Target format.
void setTargetFormat(TargetFormat format)
Set the target format.

Enum documentation

enum class Magnum::Trade::BasisImporter::TargetFormat: UnsignedInt

Type to transcode to.

If the image does not contain an alpha channel and the target format has it, alpha will be set to opaque. Conversely, for output formats without alpha the channel will be dropped.

Enumerators
Etc1RGB

ETC1 RGB. Loaded as CompressedPixelFormat::Etc2RGB8Unorm/ CompressedPixelFormat::Etc2RGB8Srgb (which ETC1 is a subset of). If the image contains an alpha channel, it will be dropped since ETC1 alone doesn't support alpha.

Etc2RGBA

ETC2 RGBA. Loaded as CompressedPixelFormat::Etc2RGBA8Unorm/ CompressedPixelFormat::Etc2RGBA8Srgb.

Bc1RGB

BC1 RGB. Loaded as CompressedPixelFormat::Bc1RGBUnorm/ CompressedPixelFormat::Bc1RGBSrgb. Punchthrough alpha mode of BC1 RGBA is not supported.

Bc3RGBA

BC3 RGBA. Loaded as CompressedPixelFormat::Bc3RGBAUnorm/ CompressedPixelFormat::Bc3RGBASrgb.

Bc4R

BC4 R. Loaded as CompressedPixelFormat::Bc4RUnorm.

Bc5RG

BC5 RG. Taken from the input red and alpha channel. Loaded as CompressedPixelFormat::Bc5RGUnorm.

Bc7RGBA

BC7 RGBA. If no alpha is present, it's set to opaque. Loaded as CompressedPixelFormat::Bc7RGBAUnorm/ CompressedPixelFormat::Bc7RGBASrgb.

PvrtcRGB4bpp

PVRTC1 RGB 4 bpp. Loaded as CompressedPixelFormat::PvrtcRGB4bppUnorm/ CompressedPixelFormat::PvrtcRGB4bppSrgb.

PvrtcRGBA4bpp

PVRTC1 RGBA 4 bpp. Loaded as CompressedPixelFormat::PvrtcRGBA4bppUnorm/ CompressedPixelFormat::PvrtcRGBA4bppSrgb.

Astc4x4RGBA

ASTC 4x4 RGBA. Loaded as CompressedPixelFormat::Astc4x4RGBAUnorm/ CompressedPixelFormat::Astc4x4RGBASrgb.

RGBA8

Uncompressed 32-bit RGBA. Loaded as PixelFormat::RGBA8Unorm / PixelFormat::RGBA8Srgb. If no concrete format is specified, the importer will fall back to this.

EacR

EAC R. Loaded as CompressedPixelFormat::EacR11Unorm.

EacRG

EAC RG. Taken from the input red and alpha channel. Loaded as CompressedPixelFormat::EacRG11Unorm.

Function documentation

static void Magnum::Trade::BasisImporter::initialize()

Initialize Basis transcoder.

If the class is instantiated directly (not through a plugin manager), this function has to be called explicitly before using any instance.

void Magnum::Trade::BasisImporter::setTargetFormat(TargetFormat format)

Set the target format.

See Target format for more information.