class new in 2019.10
BasisImporterBasis 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
, BasisImporterBc6hRGB
,, BasisImporterBc7RGBA
, BasisImporterPvrtc1RGB4bpp
, BasisImporterPvrtc1RGBA4bpp
, BasisImporterAstc4x4RGBA
, BasisImporterAstc4x4RGBAF
, BasisImporterRGBA8
, BasisImporterRGB16F
, BasisImporterRGBA16F
.
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::v1_50_0_2
1.16.4
and v1_15_update2
tags, 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, 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::
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::
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 or RGBA8. If not set, falls back to RGBA8 with # a warning. Not used for transcoding HDR images, set formatHdr for those. format= # Same as format, but for HDR images. Should be one of Bc6hRGB, Astc4x4RGBAF, # RGB16F or RGBA16F. If not set, falls back to RGBA16F with a warning. formatHdr=
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. When loading an HDR image, it will be transcoded to an HDR target format. Conversely, non-HDR images can only be transcoded to non-HDR formats. With BasisImporter, these formats can be chosen in different ways:
/* Choose ETC2 target format. Sets the format configuration option and leaves formatHdr at its default. */ Containers::Pointer<Trade::AbstractImporter> importerEtc2 = manager.instantiate("BasisImporterEtc2RGBA"); /* Choose BC5 target format */ Containers::Pointer<Trade::AbstractImporter> importerBc5 = manager.instantiate("BasisImporterBc5RG"); /* Choose BC6 target format. This is an HDR format, so sets the formatHdr configuration option and leaves format at its default. */ Containers::Pointer<Trade::AbstractImporter> importerBc6 = manager.instantiate("BasisImporterBc6hRGB");
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, set the format
and formatHdr
configuration options.
/* Instantiate the plugin under its default name. At this point, the plugin would decompress to full RGBA8/RGBA16F, which is usually not what you want. */ Containers::Pointer<Trade::AbstractImporter> importer = manager.instantiate("BasisImporter"); importer->openFile("mytexture.basis"); /* Transcode LDR images to BC5, and HDR images to ASTC4x4F */ importer->configuration().setValue("format", "Bc5RG"); importer->configuration().setValue("formatHdr", "Astc4x4RGBAF"); image = importer->image2D(0); // ... /* Transcode the same image, but to ETC2/BC6 now */ importer->configuration().setValue("format", "Etc2RGBA"); importer->configuration().setValue("formatHdr", "Bc6hRGB"); 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 /* Pseudo-extension that checks for WEBGL_compressed_texture_astc plus the presence of the LDR profile */ if(context.isExtensionSupported<MAGNUM::compressed_texture_astc_ldr>()) #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 }
Selecting an HDR format is a bit simpler:
if(PluginManager::PluginMetadata* metadata = manager.metadata("BasisImporter")) { GL::Context& context = GL::Context::current(); using namespace GL::Extensions; #ifdef MAGNUM_TARGET_WEBGL /* Pseudo-extension that checks for WEBGL_compressed_texture_astc plus the presence of the HDR profile */ if(context.isExtensionSupported<MAGNUM::compressed_texture_astc_hdr>()) #else if(context.isExtensionSupported<KHR::texture_compression_astc_hdr>()) #endif { metadata->configuration().setValue("formatHdr", "Astc4x4RGBAF"); } /* BC6 extension is available on WebGL 1 and 2, but not ES2 */ #if !defined(MAGNUM_TARGET_GLES2) || defined(MAGNUM_TARGET_WEBGL) #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("formatHdr", "Bc6hRGB"); } #endif else { /* Fall back to uncompressed if nothing else is supported */ metadata->configuration().setValue("formatHdr", "RGBA16F"); } }
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, Bc6hRGB = 22, Astc4x4RGBAF = 23, RGB16F = 24, RGBA16F = 25 }
- Format to transcode to.
Public static functions
- static void initialize()
- Initialize Basis transcoder.
Constructors, destructors, conversion operators
- BasisImporter() deprecated in Git master explicit
- Default constructor.
-
BasisImporter(PluginManager::
AbstractManager& manager, const Containers:: StringView& plugin) explicit - Plugin manager constructor.
Public functions
- auto targetFormat() const -> TargetFormat deprecated in Git master
- Target format.
- void setTargetFormat(TargetFormat format) deprecated in Git master
- Set the target format.
Enum documentation
enum class Magnum:: Trade:: BasisImporter:: TargetFormat: UnsignedInt
Format to transcode to.
Exposed for documentation purposes only. Pick the format either by loading the plugin under one of the above-listed aliases with the values as suffix, or by setting the format
and formatHdr
configuration options.
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.
Function documentation
Magnum:: Trade:: BasisImporter:: BasisImporter() explicit
Default constructor.
TargetFormat Magnum:: Trade:: BasisImporter:: targetFormat() const
Target format.
void Magnum:: Trade:: BasisImporter:: setTargetFormat(TargetFormat format)
Set the target format.