diff --git a/CMakeLists.txt b/CMakeLists.txt index 78da71f3cf..7320cdf8d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -647,6 +647,7 @@ add_subdirectory(${LIBRARIES}/gltfio) add_subdirectory(${LIBRARIES}/ibl) add_subdirectory(${LIBRARIES}/iblprefilter) add_subdirectory(${LIBRARIES}/image) +add_subdirectory(${LIBRARIES}/ktxreader) add_subdirectory(${LIBRARIES}/math) add_subdirectory(${LIBRARIES}/mathio) add_subdirectory(${LIBRARIES}/utils) diff --git a/android/filament-utils-android/CMakeLists.txt b/android/filament-utils-android/CMakeLists.txt index c5c214e216..92c4a319b3 100644 --- a/android/filament-utils-android/CMakeLists.txt +++ b/android/filament-utils-android/CMakeLists.txt @@ -14,6 +14,10 @@ add_library(image STATIC IMPORTED) set_target_properties(image PROPERTIES IMPORTED_LOCATION ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimage.a) +add_library(ktxreader STATIC IMPORTED) +set_target_properties(ktxreader PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libktxreader.a) + add_library(viewer STATIC IMPORTED) set_target_properties(viewer PROPERTIES IMPORTED_LOCATION ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libviewer.a) @@ -64,5 +68,6 @@ target_link_libraries(filament-utils-jni camutils iblprefilter image + ktxreader viewer ) diff --git a/android/filament-utils-android/src/main/cpp/Utils.cpp b/android/filament-utils-android/src/main/cpp/Utils.cpp index 445b6891b1..2af710846e 100644 --- a/android/filament-utils-android/src/main/cpp/Utils.cpp +++ b/android/filament-utils-android/src/main/cpp/Utils.cpp @@ -20,13 +20,14 @@ #include #include -#include +#include #include "common/NioUtils.h" using namespace filament; using namespace filament::math; using namespace image; +using namespace ktxreader; jlong nCreateHDRTexture(JNIEnv* env, jclass, jlong nativeEngine, jobject javaBuffer, jint remaining, jint internalFormat); @@ -35,9 +36,9 @@ static jlong nCreateKTXTexture(JNIEnv* env, jclass, jlong nativeEngine, jobject javaBuffer, jint remaining, jboolean srgb) { Engine* engine = (Engine*) nativeEngine; AutoBuffer buffer(env, javaBuffer, remaining); - KtxBundle* bundle = new KtxBundle((const uint8_t*) buffer.getData(), buffer.getSize()); - return (jlong) ktx::createTexture(engine, *bundle, srgb, [](void* userdata) { - KtxBundle* bundle = (KtxBundle*) userdata; + Ktx1Bundle* bundle = new Ktx1Bundle((const uint8_t*) buffer.getData(), buffer.getSize()); + return (jlong) Ktx1Reader::createTexture(engine, *bundle, srgb, [](void* userdata) { + Ktx1Bundle* bundle = (Ktx1Bundle*) userdata; delete bundle; }, bundle); } @@ -46,9 +47,9 @@ static jlong nCreateIndirectLight(JNIEnv* env, jclass, jlong nativeEngine, jobject javaBuffer, jint remaining, jboolean srgb) { Engine* engine = (Engine*) nativeEngine; AutoBuffer buffer(env, javaBuffer, remaining); - KtxBundle* bundle = new KtxBundle((const uint8_t*) buffer.getData(), buffer.getSize()); - Texture* cubemap = ktx::createTexture(engine, *bundle, srgb, [](void* userdata) { - KtxBundle* bundle = (KtxBundle*) userdata; + Ktx1Bundle* bundle = new Ktx1Bundle((const uint8_t*) buffer.getData(), buffer.getSize()); + Texture* cubemap = Ktx1Reader::createTexture(engine, *bundle, srgb, [](void* userdata) { + Ktx1Bundle* bundle = (Ktx1Bundle*) userdata; delete bundle; }, bundle); @@ -68,9 +69,9 @@ static jlong nCreateSkybox(JNIEnv* env, jclass, jlong nativeEngine, jobject javaBuffer, jint remaining, jboolean srgb) { Engine* engine = (Engine*) nativeEngine; AutoBuffer buffer(env, javaBuffer, remaining); - KtxBundle* bundle = new KtxBundle((const uint8_t*) buffer.getData(), buffer.getSize()); - Texture* cubemap = ktx::createTexture(engine, *bundle, srgb, [](void* userdata) { - KtxBundle* bundle = (KtxBundle*) userdata; + Ktx1Bundle* bundle = new Ktx1Bundle((const uint8_t*) buffer.getData(), buffer.getSize()); + Texture* cubemap = Ktx1Reader::createTexture(engine, *bundle, srgb, [](void* userdata) { + Ktx1Bundle* bundle = (Ktx1Bundle*) userdata; delete bundle; }, bundle); return (jlong) Skybox::Builder().environment(cubemap).showSun(true).build(*engine); @@ -79,7 +80,7 @@ static jlong nCreateSkybox(JNIEnv* env, jclass, static jboolean nGetSphericalHarmonics(JNIEnv* env, jclass, jobject javaBuffer, jint remaining, jfloatArray outSphericalHarmonics_) { AutoBuffer buffer(env, javaBuffer, remaining); - KtxBundle bundle((const uint8_t*) buffer.getData(), buffer.getSize()); + Ktx1Bundle bundle((const uint8_t*) buffer.getData(), buffer.getSize()); jfloat* outSphericalHarmonics = env->GetFloatArrayElements(outSphericalHarmonics_, nullptr); const auto success = bundle.getSphericalHarmonics( diff --git a/build/common/test_list.txt b/build/common/test_list.txt index 382927d75d..39a71c5f4f 100644 --- a/build/common/test_list.txt +++ b/build/common/test_list.txt @@ -11,3 +11,4 @@ tools/cmgen/test_cmgen compare tools/glslminifier/test_glslminifier libs/filameshio/test_filameshio libs/camutils/test_camutils +libs/ktxreader/test_ktxreader diff --git a/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm b/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm index 5ae108d847..0da5c269e8 100644 --- a/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm +++ b/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm @@ -25,13 +25,14 @@ #include -#include +#include #include #include using namespace filament; using namespace utils; +using namespace ktxreader; @interface FILViewController () @@ -169,10 +170,10 @@ using namespace utils; assert(skyboxPath.length > 0); NSData* skyboxBuffer = [NSData dataWithContentsOfFile:skyboxPath]; - image::KtxBundle* skyboxBundle = - new image::KtxBundle(static_cast(skyboxBuffer.bytes), + image::Ktx1Bundle* skyboxBundle = + new image::Ktx1Bundle(static_cast(skyboxBuffer.bytes), static_cast(skyboxBuffer.length)); - _skyboxTexture = image::ktx::createTexture(self.modelView.engine, skyboxBundle, false); + _skyboxTexture = Ktx1Reader::createTexture(self.modelView.engine, skyboxBundle, false); _skybox = filament::Skybox::Builder().environment(_skyboxTexture).build(*self.modelView.engine); self.modelView.scene->setSkybox(_skybox); @@ -181,11 +182,11 @@ using namespace utils; assert(iblPath.length > 0); NSData* iblBuffer = [NSData dataWithContentsOfFile:iblPath]; - image::KtxBundle* iblBundle = new image::KtxBundle( + image::Ktx1Bundle* iblBundle = new image::Ktx1Bundle( static_cast(iblBuffer.bytes), static_cast(iblBuffer.length)); math::float3 harmonics[9]; iblBundle->getSphericalHarmonics(harmonics); - _iblTexture = image::ktx::createTexture(self.modelView.engine, iblBundle, false); + _iblTexture = Ktx1Reader::createTexture(self.modelView.engine, iblBundle, false); _indirectLight = IndirectLight::Builder() .reflections(_iblTexture) .irradiance(3, harmonics) diff --git a/ios/samples/hello-ar/hello-ar/FilamentArView/FilamentApp.cpp b/ios/samples/hello-ar/hello-ar/FilamentArView/FilamentApp.cpp index 12d687b4c5..aa8038d965 100644 --- a/ios/samples/hello-ar/hello-ar/FilamentArView/FilamentApp.cpp +++ b/ios/samples/hello-ar/hello-ar/FilamentArView/FilamentApp.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include @@ -35,6 +35,7 @@ #include "resources.h" using namespace filamesh; +using namespace ktxreader; static constexpr float OBJECT_SCALE = 0.02f; @@ -205,11 +206,11 @@ void FilamentApp::setupFilament() { } void FilamentApp::setupIbl() { - image::KtxBundle* iblBundle = new image::KtxBundle(RESOURCES_VENETIAN_CROSSROADS_2K_IBL_DATA, + image::Ktx1Bundle* iblBundle = new image::Ktx1Bundle(RESOURCES_VENETIAN_CROSSROADS_2K_IBL_DATA, RESOURCES_VENETIAN_CROSSROADS_2K_IBL_SIZE); float3 harmonics[9]; iblBundle->getSphericalHarmonics(harmonics); - app.iblTexture = image::ktx::createTexture(engine, iblBundle, false); + app.iblTexture = Ktx1Reader::createTexture(engine, iblBundle, false); app.indirectLight = IndirectLight::Builder() .reflections(app.iblTexture) diff --git a/ios/samples/hello-gltf/hello-gltf/FilamentView/App.cpp b/ios/samples/hello-gltf/hello-gltf/FilamentView/App.cpp index 5a636934fd..c28a068824 100644 --- a/ios/samples/hello-gltf/hello-gltf/FilamentView/App.cpp +++ b/ios/samples/hello-gltf/hello-gltf/FilamentView/App.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -32,6 +32,8 @@ // textures this app uses. #include "resources.h" +using namespace ktxreader; + App::App(void* nativeLayer, uint32_t width, uint32_t height, const utils::Path& resourcePath) : nativeLayer(nativeLayer), width(width), height(height), resourcePath(resourcePath) { setupFilament(); @@ -89,15 +91,15 @@ void App::setupFilament() { } void App::setupIbl() { - image::KtxBundle* iblBundle = new image::KtxBundle(RESOURCES_VENETIAN_CROSSROADS_2K_IBL_DATA, + image::Ktx1Bundle* iblBundle = new image::Ktx1Bundle(RESOURCES_VENETIAN_CROSSROADS_2K_IBL_DATA, RESOURCES_VENETIAN_CROSSROADS_2K_IBL_SIZE); float3 harmonics[9]; iblBundle->getSphericalHarmonics(harmonics); - app.iblTexture = image::ktx::createTexture(engine, iblBundle, false); + app.iblTexture = Ktx1Reader::createTexture(engine, iblBundle, false); - image::KtxBundle* skyboxBundle = new image::KtxBundle(RESOURCES_VENETIAN_CROSSROADS_2K_SKYBOX_DATA, + image::Ktx1Bundle* skyboxBundle = new image::Ktx1Bundle(RESOURCES_VENETIAN_CROSSROADS_2K_SKYBOX_DATA, RESOURCES_VENETIAN_CROSSROADS_2K_SKYBOX_SIZE); - app.skyboxTexture = image::ktx::createTexture(engine, skyboxBundle, false); + app.skyboxTexture = Ktx1Reader::createTexture(engine, skyboxBundle, false); app.skybox = Skybox::Builder() .environment(app.skyboxTexture) diff --git a/ios/samples/hello-pbr/hello-pbr/FilamentApp.cpp b/ios/samples/hello-pbr/hello-pbr/FilamentApp.cpp index 56ed748716..0801065534 100644 --- a/ios/samples/hello-pbr/hello-pbr/FilamentApp.cpp +++ b/ios/samples/hello-pbr/hello-pbr/FilamentApp.cpp @@ -21,7 +21,7 @@ #include -#include +#include #include @@ -29,6 +29,8 @@ // textures this app uses. #include "resources.h" +using namespace ktxreader; + using namespace filament; using namespace filamesh; @@ -46,15 +48,15 @@ void FilamentApp::initialize() { filaView = engine->createView(); - image::KtxBundle* iblBundle = new image::KtxBundle(RESOURCES_VENETIAN_CROSSROADS_2K_IBL_DATA, + image::Ktx1Bundle* iblBundle = new image::Ktx1Bundle(RESOURCES_VENETIAN_CROSSROADS_2K_IBL_DATA, RESOURCES_VENETIAN_CROSSROADS_2K_IBL_SIZE); filament::math::float3 harmonics[9]; iblBundle->getSphericalHarmonics(harmonics); - app.iblTexture = image::ktx::createTexture(engine, iblBundle, false); + app.iblTexture = Ktx1Reader::createTexture(engine, iblBundle, false); - image::KtxBundle* skyboxBundle = new image::KtxBundle(RESOURCES_VENETIAN_CROSSROADS_2K_SKYBOX_DATA, + image::Ktx1Bundle* skyboxBundle = new image::Ktx1Bundle(RESOURCES_VENETIAN_CROSSROADS_2K_SKYBOX_DATA, RESOURCES_VENETIAN_CROSSROADS_2K_SKYBOX_SIZE); - app.skyboxTexture = image::ktx::createTexture(engine, skyboxBundle, false); + app.skyboxTexture = Ktx1Reader::createTexture(engine, skyboxBundle, false); app.skybox = Skybox::Builder() .environment(app.skyboxTexture) diff --git a/libs/filamentapp/CMakeLists.txt b/libs/filamentapp/CMakeLists.txt index 0c2266328a..7414278ce9 100644 --- a/libs/filamentapp/CMakeLists.txt +++ b/libs/filamentapp/CMakeLists.txt @@ -40,19 +40,20 @@ set(SRCS set(LIBS assimp + camutils + filagui + filamat filament filament-iblprefilter + geometry + getopt + image + imgui + ktxreader + math sdl2 stb - math - filamat utils - getopt - imgui - filagui - image - camutils - geometry ) set(MATERIAL_SRCS diff --git a/libs/filamentapp/src/IBL.cpp b/libs/filamentapp/src/IBL.cpp index 8b90ed6951..0b413c5af6 100644 --- a/libs/filamentapp/src/IBL.cpp +++ b/libs/filamentapp/src/IBL.cpp @@ -23,8 +23,7 @@ #include #include -#include -#include +#include #include @@ -39,8 +38,8 @@ #include using namespace filament; -using namespace image; using namespace filament::math; +using namespace ktxreader; using namespace utils; static constexpr float IBL_INTENSITY = 30000.0f; @@ -128,14 +127,14 @@ bool IBL::loadFromKtx(const std::string& prefix) { using namespace std; ifstream file(path.getPath(), ios::binary); vector contents((istreambuf_iterator(file)), {}); - return new image::KtxBundle(contents.data(), contents.size()); + return new image::Ktx1Bundle(contents.data(), contents.size()); }; - KtxBundle* iblKtx = createKtx(iblPath); - KtxBundle* skyKtx = createKtx(skyPath); + Ktx1Bundle* iblKtx = createKtx(iblPath); + Ktx1Bundle* skyKtx = createKtx(skyPath); - mSkyboxTexture = ktx::createTexture(&mEngine, skyKtx, false); - mTexture = ktx::createTexture(&mEngine, iblKtx, false); + mSkyboxTexture = Ktx1Reader::createTexture(&mEngine, skyKtx, false); + mTexture = Ktx1Reader::createTexture(&mEngine, iblKtx, false); if (!iblKtx->getSphericalHarmonics(mBands)) { return false; diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 98394e36b9..e4bf2882c4 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -11,15 +11,14 @@ set(PUBLIC_HDRS include/image/ColorTransform.h include/image/ImageOps.h include/image/ImageSampler.h - include/image/KtxBundle.h - include/image/KtxUtility.h + include/image/Ktx1Bundle.h include/image/LinearImage.h ) set(SRCS src/ImageOps.cpp src/ImageSampler.cpp - src/KtxBundle.cpp + src/Ktx1Bundle.cpp src/LinearImage.cpp ) @@ -44,12 +43,6 @@ else() target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register) endif() -# ================================================================================================== -# Expose header-only utilities to lean-and-mean client projects. -# ================================================================================================== -add_library(image_headers INTERFACE) -target_include_directories(image_headers INTERFACE ${PUBLIC_HDR_DIR}) - # ================================================================================================== # Installation # ================================================================================================== diff --git a/libs/image/include/image/KtxBundle.h b/libs/image/include/image/Ktx1Bundle.h similarity index 96% rename from libs/image/include/image/KtxBundle.h rename to libs/image/include/image/Ktx1Bundle.h index 3975a6441d..d408dc7c24 100644 --- a/libs/image/include/image/KtxBundle.h +++ b/libs/image/include/image/Ktx1Bundle.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef IMAGE_KTXBUNDLE_H -#define IMAGE_KTXBUNDLE_H +#ifndef IMAGE_KTX1BUNDLE_H +#define IMAGE_KTX1BUNDLE_H #include @@ -48,7 +48,7 @@ struct KtxBlobList; struct KtxMetadata; /** - * KtxBundle is a structured set of opaque data blobs that can be passed straight to the GPU, such + * Ktx1Bundle is a structured set of opaque data blobs that can be passed straight to the GPU, such * that a single bundle corresponds to a single texture object. It is well suited for storing * block-compressed texture data. * @@ -63,22 +63,22 @@ struct KtxMetadata; * * https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ */ -class UTILS_PUBLIC KtxBundle { +class UTILS_PUBLIC Ktx1Bundle { public: - ~KtxBundle(); + ~Ktx1Bundle(); /** * Creates a hierarchy of empty texture blobs, to be filled later via setBlob(). */ - KtxBundle(uint32_t numMipLevels, uint32_t arrayLength, bool isCubemap); + Ktx1Bundle(uint32_t numMipLevels, uint32_t arrayLength, bool isCubemap); /** * Creates a new bundle by deserializing the given data. * * Typically, this constructor is used to consume the contents of a KTX file. */ - KtxBundle(uint8_t const* bytes, uint32_t nbytes); + Ktx1Bundle(uint8_t const* bytes, uint32_t nbytes); /** * Serializes the bundle into the given target memory. Returns false if there's not enough @@ -276,4 +276,4 @@ private: } // namespace image -#endif /* IMAGE_KTXBUNDLE_H */ +#endif /* IMAGE_Ktx1Bundle_H */ diff --git a/libs/image/include/image/KtxUtility.h b/libs/image/include/image/KtxUtility.h deleted file mode 100644 index 7ff4d89f81..0000000000 --- a/libs/image/include/image/KtxUtility.h +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IMAGE_KTXUTILITY_H -#define IMAGE_KTXUTILITY_H - -#include -#include - -#include - -namespace image { - -/** - * Allows clients to create Filament textures from KtxBundle objects. - * - * Note that libimage does not have a dependency on libfilament, so for simplicity this is a - * header-only library with inlined functions. - */ -namespace ktx { - - using Texture = filament::Texture; - using Engine = filament::Engine; - - using TextureFormat = Texture::InternalFormat; - using CompressedPixelDataType = Texture::CompressedType; - using PixelDataType = Texture::Type; - using PixelDataFormat = Texture::Format; - using PixelBufferDescriptor = Texture::PixelBufferDescriptor; - - using Callback = void(*)(void* userdata); - - CompressedPixelDataType toCompressedPixelDataType(const KtxInfo& info); - PixelDataType toPixelDataType(const KtxInfo& info); - PixelDataFormat toPixelDataFormat(const KtxInfo& info); - bool isCompressed(const KtxInfo& info); - TextureFormat toTextureFormat(const KtxInfo& info); - TextureFormat toSrgbTextureFormat(TextureFormat tex); - - /** - * Creates a Texture object from a KTX file and populates all of its faces and miplevels. - * - * @param engine Used to create the Filament Texture - * @param ktx In-memory representation of a KTX file - * @param srgb Forces the KTX-specified format into an SRGB format if possible - * @param callback Gets called after all texture data has been uploaded to the GPU - * @param userdata Passed into the callback - */ - inline Texture* createTexture(Engine* engine, const KtxBundle& ktx, bool srgb, - Callback callback, void* userdata) { - using Sampler = Texture::Sampler; - const auto& ktxinfo = ktx.getInfo(); - const uint32_t nmips = ktx.getNumMipLevels(); - const auto cdatatype = toCompressedPixelDataType(ktxinfo); - const auto datatype = toPixelDataType(ktxinfo); - const auto dataformat = toPixelDataFormat(ktxinfo); - - auto texformat = toTextureFormat(ktxinfo); - if (srgb) { - texformat = toSrgbTextureFormat(texformat); - } - - Texture* texture = Texture::Builder() - .width(ktxinfo.pixelWidth) - .height(ktxinfo.pixelHeight) - .levels(static_cast(nmips)) - .sampler(ktx.isCubemap() ? Sampler::SAMPLER_CUBEMAP : Sampler::SAMPLER_2D) - .format(texformat) - .build(*engine); - - struct Userdata { - uint32_t remainingBuffers; - Callback callback; - void* userdata; - }; - - Userdata* cbuser = new Userdata({nmips, callback, userdata}); - - PixelBufferDescriptor::Callback cb = [](void*, size_t, void* cbuserptr) { - Userdata* cbuser = (Userdata*) cbuserptr; - if (--cbuser->remainingBuffers == 0) { - if (cbuser->callback) { - cbuser->callback(cbuser->userdata); - } - delete cbuser; - } - }; - - uint8_t* data; - uint32_t size; - - if (isCompressed(ktxinfo)) { - if (ktx.isCubemap()) { - for (uint32_t level = 0; level < nmips; ++level) { - ktx.getBlob({level, 0, 0}, &data, &size); - PixelBufferDescriptor pbd(data, size * 6, cdatatype, size, cb, cbuser); - texture->setImage(*engine, level, std::move(pbd), Texture::FaceOffsets(size)); - } - return texture; - } - for (uint32_t level = 0; level < nmips; ++level) { - ktx.getBlob({level, 0, 0}, &data, &size); - PixelBufferDescriptor pbd(data, size, cdatatype, size, cb, cbuser); - texture->setImage(*engine, level, std::move(pbd)); - } - return texture; - } - - if (ktx.isCubemap()) { - for (uint32_t level = 0; level < nmips; ++level) { - ktx.getBlob({level, 0, 0}, &data, &size); - PixelBufferDescriptor pbd(data, size * 6, dataformat, datatype, cb, cbuser); - texture->setImage(*engine, level, std::move(pbd), Texture::FaceOffsets(size)); - } - return texture; - } - - for (uint32_t level = 0; level < nmips; ++level) { - ktx.getBlob({level, 0, 0}, &data, &size); - PixelBufferDescriptor pbd(data, size, dataformat, datatype, cb, cbuser); - texture->setImage(*engine, level, std::move(pbd)); - } - return texture; - } - - /** - * Creates a Texture object from a KTX bundle, populates all of its faces and miplevels, - * and automatically destroys the bundle after all the texture data has been uploaded. - * - * @param engine Used to create the Filament Texture - * @param ktx In-memory representation of a KTX file - * @param srgb Forces the KTX-specified format into an SRGB format if possible - */ - inline Texture* createTexture(Engine* engine, KtxBundle* ktx, bool srgb) { - auto freeKtx = [] (void* userdata) { - KtxBundle* ktx = (KtxBundle*) userdata; - delete ktx; - }; - return createTexture(engine, *ktx, srgb, freeKtx, ktx); - } - - template - T toCompressedFilamentEnum(uint32_t format) { - switch (format) { - case KtxBundle::RGB_S3TC_DXT1: return T::DXT1_RGB; - case KtxBundle::RGBA_S3TC_DXT1: return T::DXT1_RGBA; - case KtxBundle::RGBA_S3TC_DXT3: return T::DXT3_RGBA; - case KtxBundle::RGBA_S3TC_DXT5: return T::DXT5_RGBA; - case KtxBundle::RGBA_ASTC_4x4: return T::RGBA_ASTC_4x4; - case KtxBundle::RGBA_ASTC_5x4: return T::RGBA_ASTC_5x4; - case KtxBundle::RGBA_ASTC_5x5: return T::RGBA_ASTC_5x5; - case KtxBundle::RGBA_ASTC_6x5: return T::RGBA_ASTC_6x5; - case KtxBundle::RGBA_ASTC_6x6: return T::RGBA_ASTC_6x6; - case KtxBundle::RGBA_ASTC_8x5: return T::RGBA_ASTC_8x5; - case KtxBundle::RGBA_ASTC_8x6: return T::RGBA_ASTC_8x6; - case KtxBundle::RGBA_ASTC_8x8: return T::RGBA_ASTC_8x8; - case KtxBundle::RGBA_ASTC_10x5: return T::RGBA_ASTC_10x5; - case KtxBundle::RGBA_ASTC_10x6: return T::RGBA_ASTC_10x6; - case KtxBundle::RGBA_ASTC_10x8: return T::RGBA_ASTC_10x8; - case KtxBundle::RGBA_ASTC_10x10: return T::RGBA_ASTC_10x10; - case KtxBundle::RGBA_ASTC_12x10: return T::RGBA_ASTC_12x10; - case KtxBundle::RGBA_ASTC_12x12: return T::RGBA_ASTC_12x12; - case KtxBundle::SRGB8_ALPHA8_ASTC_4x4: return T::SRGB8_ALPHA8_ASTC_4x4; - case KtxBundle::SRGB8_ALPHA8_ASTC_5x4: return T::SRGB8_ALPHA8_ASTC_5x4; - case KtxBundle::SRGB8_ALPHA8_ASTC_5x5: return T::SRGB8_ALPHA8_ASTC_5x5; - case KtxBundle::SRGB8_ALPHA8_ASTC_6x5: return T::SRGB8_ALPHA8_ASTC_6x5; - case KtxBundle::SRGB8_ALPHA8_ASTC_6x6: return T::SRGB8_ALPHA8_ASTC_6x6; - case KtxBundle::SRGB8_ALPHA8_ASTC_8x5: return T::SRGB8_ALPHA8_ASTC_8x5; - case KtxBundle::SRGB8_ALPHA8_ASTC_8x6: return T::SRGB8_ALPHA8_ASTC_8x6; - case KtxBundle::SRGB8_ALPHA8_ASTC_8x8: return T::SRGB8_ALPHA8_ASTC_8x8; - case KtxBundle::SRGB8_ALPHA8_ASTC_10x5: return T::SRGB8_ALPHA8_ASTC_10x5; - case KtxBundle::SRGB8_ALPHA8_ASTC_10x6: return T::SRGB8_ALPHA8_ASTC_10x6; - case KtxBundle::SRGB8_ALPHA8_ASTC_10x8: return T::SRGB8_ALPHA8_ASTC_10x8; - case KtxBundle::SRGB8_ALPHA8_ASTC_10x10: return T::SRGB8_ALPHA8_ASTC_10x10; - case KtxBundle::SRGB8_ALPHA8_ASTC_12x10: return T::SRGB8_ALPHA8_ASTC_12x10; - case KtxBundle::SRGB8_ALPHA8_ASTC_12x12: return T::SRGB8_ALPHA8_ASTC_12x12; - case KtxBundle::R11_EAC: return T::EAC_R11; - case KtxBundle::SIGNED_R11_EAC: return T::EAC_R11_SIGNED; - case KtxBundle::RG11_EAC: return T::EAC_RG11; - case KtxBundle::SIGNED_RG11_EAC: return T::EAC_RG11_SIGNED; - case KtxBundle::RGB8_ETC2: return T::ETC2_RGB8; - case KtxBundle::SRGB8_ETC2: return T::ETC2_SRGB8; - case KtxBundle::RGB8_ALPHA1_ETC2: return T::ETC2_RGB8_A1; - case KtxBundle::SRGB8_ALPHA1_ETC: return T::ETC2_SRGB8_A1; - case KtxBundle::RGBA8_ETC2_EAC: return T::ETC2_EAC_RGBA8; - case KtxBundle::SRGB8_ALPHA8_ETC2_EAC: return T::ETC2_EAC_SRGBA8; - } - return (T) 0xffff; - } - - inline CompressedPixelDataType toCompressedPixelDataType(const KtxInfo& info) { - return toCompressedFilamentEnum(info.glInternalFormat); - } - - inline PixelDataType toPixelDataType(const KtxInfo& info) { - switch (info.glType) { - case KtxBundle::UNSIGNED_BYTE: return PixelDataType::UBYTE; - case KtxBundle::UNSIGNED_SHORT: return PixelDataType::USHORT; - case KtxBundle::HALF_FLOAT: return PixelDataType::HALF; - case KtxBundle::FLOAT: return PixelDataType::FLOAT; - case KtxBundle::R11F_G11F_B10F: return PixelDataType::UINT_10F_11F_11F_REV; - } - return (PixelDataType) 0xff; - } - - inline PixelDataFormat toPixelDataFormat(const KtxInfo& info) { - switch (info.glFormat) { - case KtxBundle::LUMINANCE: - case KtxBundle::RED: return PixelDataFormat::R; - case KtxBundle::RG: return PixelDataFormat::RG; - case KtxBundle::RGB: return PixelDataFormat::RGB; - case KtxBundle::RGBA: return PixelDataFormat::RGBA; - // glFormat should NOT be a sized format according to the spec - // however cmgen was generating incorrect files until after Filament 1.8.0 - // so we keep this line here to preserve compatibility with older assets - case KtxBundle::R11F_G11F_B10F: return PixelDataFormat::RGB; - } - return (PixelDataFormat) 0xff; - } - - inline bool isCompressed(const KtxInfo& info) { - return info.glFormat == 0; - } - - inline TextureFormat toSrgbTextureFormat(TextureFormat format) { - switch(format) { - // Non-compressed - case Texture::InternalFormat::RGB8: - return Texture::InternalFormat::SRGB8; - case Texture::InternalFormat::RGBA8: - return Texture::InternalFormat::SRGB8_A8; - - // ASTC - case Texture::InternalFormat::RGBA_ASTC_4x4: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_4x4; - case Texture::InternalFormat::RGBA_ASTC_5x4: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_5x4; - case Texture::InternalFormat::RGBA_ASTC_5x5: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_5x5; - case Texture::InternalFormat::RGBA_ASTC_6x5: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_6x5; - case Texture::InternalFormat::RGBA_ASTC_6x6: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_6x6; - case Texture::InternalFormat::RGBA_ASTC_8x5: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_8x5; - case Texture::InternalFormat::RGBA_ASTC_8x6: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_8x6; - case Texture::InternalFormat::RGBA_ASTC_8x8: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_8x8; - case Texture::InternalFormat::RGBA_ASTC_10x5: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_10x5; - case Texture::InternalFormat::RGBA_ASTC_10x6: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_10x6; - case Texture::InternalFormat::RGBA_ASTC_10x8: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_10x8; - case Texture::InternalFormat::RGBA_ASTC_10x10: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_10x10; - case Texture::InternalFormat::RGBA_ASTC_12x10: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_12x10; - case Texture::InternalFormat::RGBA_ASTC_12x12: - return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_12x12; - - // ETC2 - case Texture::InternalFormat::ETC2_RGB8: - return Texture::InternalFormat::ETC2_SRGB8; - case Texture::InternalFormat::ETC2_RGB8_A1: - return Texture::InternalFormat::ETC2_SRGB8_A1; - case Texture::InternalFormat::ETC2_EAC_RGBA8: - return Texture::InternalFormat::ETC2_EAC_SRGBA8; - - // DXT - case Texture::InternalFormat::DXT1_RGB: - return Texture::InternalFormat::DXT1_SRGB; - case Texture::InternalFormat::DXT1_RGBA: - return Texture::InternalFormat::DXT1_SRGBA; - case Texture::InternalFormat::DXT3_RGBA: - return Texture::InternalFormat::DXT3_SRGBA; - case Texture::InternalFormat::DXT5_RGBA: - return Texture::InternalFormat::DXT5_SRGBA; - - default: - return format; - } - } - - inline TextureFormat toTextureFormat(const KtxInfo& info) { - switch (info.glInternalFormat) { - case KtxBundle::RED: return TextureFormat::R8; - case KtxBundle::RG: return TextureFormat::RG8; - case KtxBundle::RGB: return TextureFormat::RGB8; - case KtxBundle::RGBA: return TextureFormat::RGBA8; - case KtxBundle::LUMINANCE: return TextureFormat::R8; - case KtxBundle::LUMINANCE_ALPHA: return TextureFormat::RG8; - case KtxBundle::R8: return TextureFormat::R8; - case KtxBundle::R8_SNORM: return TextureFormat::R8_SNORM; - case KtxBundle::R8UI: return TextureFormat::R8UI; - case KtxBundle::R8I: return TextureFormat::R8I; - case KtxBundle::STENCIL_INDEX8: return TextureFormat::STENCIL8; - case KtxBundle::R16F: return TextureFormat::R16F; - case KtxBundle::R16UI: return TextureFormat::R16UI; - case KtxBundle::R16I: return TextureFormat::R16I; - case KtxBundle::RG8: return TextureFormat::RG8; - case KtxBundle::RG8_SNORM: return TextureFormat::RG8_SNORM; - case KtxBundle::RG8UI: return TextureFormat::RG8UI; - case KtxBundle::RG8I: return TextureFormat::RG8I; - case KtxBundle::RGB565: return TextureFormat::RGB565; - case KtxBundle::RGB9_E5: return TextureFormat::RGB9_E5; - case KtxBundle::RGB5_A1: return TextureFormat::RGB5_A1; - case KtxBundle::RGBA4: return TextureFormat::RGBA4; - case KtxBundle::DEPTH_COMPONENT16: return TextureFormat::DEPTH16; - case KtxBundle::RGB8: return TextureFormat::RGB8; - case KtxBundle::SRGB8: return TextureFormat::SRGB8; - case KtxBundle::RGB8_SNORM: return TextureFormat::RGB8_SNORM; - case KtxBundle::RGB8UI: return TextureFormat::RGB8UI; - case KtxBundle::RGB8I: return TextureFormat::RGB8I; - case KtxBundle::R32F: return TextureFormat::R32F; - case KtxBundle::R32UI: return TextureFormat::R32UI; - case KtxBundle::R32I: return TextureFormat::R32I; - case KtxBundle::RG16F: return TextureFormat::RG16F; - case KtxBundle::RG16UI: return TextureFormat::RG16UI; - case KtxBundle::RG16I: return TextureFormat::RG16I; - case KtxBundle::R11F_G11F_B10F: return TextureFormat::R11F_G11F_B10F; - case KtxBundle::RGBA8: return TextureFormat::RGBA8; - case KtxBundle::SRGB8_ALPHA8: return TextureFormat::SRGB8_A8; - case KtxBundle::RGBA8_SNORM: return TextureFormat::RGBA8_SNORM; - case KtxBundle::RGB10_A2: return TextureFormat::RGB10_A2; - case KtxBundle::RGBA8UI: return TextureFormat::RGBA8UI; - case KtxBundle::RGBA8I: return TextureFormat::RGBA8I; - case KtxBundle::DEPTH24_STENCIL8: return TextureFormat::DEPTH24_STENCIL8; - case KtxBundle::DEPTH32F_STENCIL8: return TextureFormat::DEPTH32F_STENCIL8; - case KtxBundle::RGB16F: return TextureFormat::RGB16F; - case KtxBundle::RGB16UI: return TextureFormat::RGB16UI; - case KtxBundle::RGB16I: return TextureFormat::RGB16I; - case KtxBundle::RG32F: return TextureFormat::RG32F; - case KtxBundle::RG32UI: return TextureFormat::RG32UI; - case KtxBundle::RG32I: return TextureFormat::RG32I; - case KtxBundle::RGBA16F: return TextureFormat::RGBA16F; - case KtxBundle::RGBA16UI: return TextureFormat::RGBA16UI; - case KtxBundle::RGBA16I: return TextureFormat::RGBA16I; - case KtxBundle::RGB32F: return TextureFormat::RGB32F; - case KtxBundle::RGB32UI: return TextureFormat::RGB32UI; - case KtxBundle::RGB32I: return TextureFormat::RGB32I; - case KtxBundle::RGBA32F: return TextureFormat::RGBA32F; - case KtxBundle::RGBA32UI: return TextureFormat::RGBA32UI; - case KtxBundle::RGBA32I: return TextureFormat::RGBA32I; - } - return toCompressedFilamentEnum(info.glInternalFormat); - } - -} // namespace ktx - -} // namespace image - -#endif diff --git a/libs/image/src/KtxBundle.cpp b/libs/image/src/Ktx1Bundle.cpp similarity index 92% rename from libs/image/src/KtxBundle.cpp rename to libs/image/src/Ktx1Bundle.cpp index 78238fb001..ab7f01ca5b 100644 --- a/libs/image/src/KtxBundle.cpp +++ b/libs/image/src/Ktx1Bundle.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include @@ -37,7 +37,7 @@ struct SerializationHeader { static_assert(sizeof(SerializationHeader) == 16 * 4, "Unexpected header size."); // We flatten the three-dimensional blob index using the ordering defined by the KTX spec. -inline size_t flatten(const image::KtxBundle* bundle, image::KtxBlobIndex index) { +inline size_t flatten(const image::Ktx1Bundle* bundle, image::KtxBlobIndex index) { const uint32_t nfaces = bundle->isCubemap() ? 6 : 1; const uint32_t nlayers = bundle->getArrayLength(); return index.cubeFace + index.arrayIndex * nfaces + index.mipLevel * nfaces * nlayers; @@ -97,9 +97,9 @@ struct KtxBlobList { } }; -KtxBundle::~KtxBundle() = default; +Ktx1Bundle::~Ktx1Bundle() = default; -KtxBundle::KtxBundle(uint32_t numMipLevels, uint32_t arrayLength, bool isCubemap) : +Ktx1Bundle::Ktx1Bundle(uint32_t numMipLevels, uint32_t arrayLength, bool isCubemap) : mBlobs(new KtxBlobList), mMetadata(new KtxMetadata) { mNumMipLevels = numMipLevels; mArrayLength = arrayLength; @@ -107,7 +107,7 @@ KtxBundle::KtxBundle(uint32_t numMipLevels, uint32_t arrayLength, bool isCubemap mBlobs->sizes.resize(numMipLevels * arrayLength * mNumCubeFaces); } -KtxBundle::KtxBundle(uint8_t const* bytes, uint32_t nbytes) : +Ktx1Bundle::Ktx1Bundle(uint8_t const* bytes, uint32_t nbytes) : mBlobs(new KtxBlobList), mMetadata(new KtxMetadata) { ASSERT_PRECONDITION(sizeof(SerializationHeader) <= nbytes, "KTX buffer is too small"); @@ -170,7 +170,7 @@ KtxBundle::KtxBundle(uint8_t const* bytes, uint32_t nbytes) : } } -bool KtxBundle::serialize(uint8_t* destination, uint32_t numBytes) const { +bool Ktx1Bundle::serialize(uint8_t* destination, uint32_t numBytes) const { uint32_t requiredLength = getSerializedLength(); if (numBytes < requiredLength) { return false; @@ -184,7 +184,7 @@ bool KtxBundle::serialize(uint8_t* destination, uint32_t numBytes) const { header.numberOfArrayElements = mArrayLength; header.numberOfFaces = mNumCubeFaces; - // For simplicity, KtxBundle does not allow non-zero array length, but to be conformant we + // For simplicity, Ktx1Bundle does not allow non-zero array length, but to be conformant we // should set this field to zero for non-array textures. if (mArrayLength == 1) { header.numberOfArrayElements = 0; @@ -247,7 +247,7 @@ bool KtxBundle::serialize(uint8_t* destination, uint32_t numBytes) const { return true; } -uint32_t KtxBundle::getSerializedLength() const { +uint32_t Ktx1Bundle::getSerializedLength() const { uint32_t total = sizeof(SerializationHeader); for (const auto& iter : mMetadata->keyvals) { const uint32_t kvsize = iter.first.size() + 1 + iter.second.size(); @@ -271,7 +271,7 @@ uint32_t KtxBundle::getSerializedLength() const { return total; } -const char* KtxBundle::getMetadata(const char* key, size_t* valueSize) const { +const char* Ktx1Bundle::getMetadata(const char* key, size_t* valueSize) const { auto iter = mMetadata->keyvals.find(key); if (iter == mMetadata->keyvals.end()) { return nullptr; @@ -283,11 +283,11 @@ const char* KtxBundle::getMetadata(const char* key, size_t* valueSize) const { return iter->second.data(); } -void KtxBundle::setMetadata(const char* key, const char* value) { +void Ktx1Bundle::setMetadata(const char* key, const char* value) { mMetadata->keyvals.insert({key, value}); } -bool KtxBundle::getSphericalHarmonics(filament::math::float3* result) { +bool Ktx1Bundle::getSphericalHarmonics(filament::math::float3* result) { char const* src = getMetadata("sh"); if (!src) { return false; @@ -305,7 +305,7 @@ bool KtxBundle::getSphericalHarmonics(filament::math::float3* result) { return true; } -bool KtxBundle::getBlob(KtxBlobIndex index, uint8_t** data, uint32_t* size) const { +bool Ktx1Bundle::getBlob(KtxBlobIndex index, uint8_t** data, uint32_t* size) const { if (index.mipLevel >= mNumMipLevels || index.arrayIndex >= mArrayLength || index.cubeFace >= mNumCubeFaces) { return false; @@ -320,7 +320,7 @@ bool KtxBundle::getBlob(KtxBlobIndex index, uint8_t** data, uint32_t* size) cons return true; } -bool KtxBundle::setBlob(KtxBlobIndex index, uint8_t const* data, uint32_t size) { +bool Ktx1Bundle::setBlob(KtxBlobIndex index, uint8_t const* data, uint32_t size) { if (index.mipLevel >= mNumMipLevels || index.arrayIndex >= mArrayLength || index.cubeFace >= mNumCubeFaces) { return false; @@ -334,7 +334,7 @@ bool KtxBundle::setBlob(KtxBlobIndex index, uint8_t const* data, uint32_t size) return true; } -bool KtxBundle::allocateBlob(KtxBlobIndex index, uint32_t size) { +bool Ktx1Bundle::allocateBlob(KtxBlobIndex index, uint32_t size) { if (index.mipLevel >= mNumMipLevels || index.arrayIndex >= mArrayLength || index.cubeFace >= mNumCubeFaces) { return false; diff --git a/libs/image/tests/test_image.cpp b/libs/image/tests/test_image.cpp index 963515a414..a0594a17e7 100644 --- a/libs/image/tests/test_image.cpp +++ b/libs/image/tests/test_image.cpp @@ -15,7 +15,7 @@ */ #include -#include +#include #include #include #include @@ -366,7 +366,7 @@ TEST_F(ImageTest, Ktx) { // NOLINT uint8_t foo[] = {1, 2, 3}; uint8_t* data; uint32_t size; - KtxBundle nascent(2, 1, true); + Ktx1Bundle nascent(2, 1, true); ASSERT_EQ(nascent.getNumMipLevels(), 2); ASSERT_EQ(nascent.getArrayLength(), 1); ASSERT_TRUE(nascent.isCubemap()); @@ -390,7 +390,7 @@ TEST_F(ImageTest, Ktx) { // NOLINT vector buffer(fileSize); std::ifstream in(path, std::ifstream::in | std::ifstream::binary); ASSERT_TRUE(in.read((char*) buffer.data(), fileSize)); - KtxBundle deserialized(buffer.data(), buffer.size()); + Ktx1Bundle deserialized(buffer.data(), buffer.size()); ASSERT_EQ(deserialized.getNumMipLevels(), 1); ASSERT_EQ(deserialized.getArrayLength(), 1); @@ -421,14 +421,14 @@ TEST_F(ImageTest, Ktx) { // NOLINT reserialized.resize(serializedSize); ASSERT_TRUE(deserialized.serialize(reserialized.data(), serializedSize)); - KtxBundle bundleWithMetadata(reserialized.data(), reserialized.size()); + Ktx1Bundle bundleWithMetadata(reserialized.data(), reserialized.size()); val = string(bundleWithMetadata.getMetadata("foo")); ASSERT_EQ(val, "bar"); } } TEST_F(ImageTest, getSphericalHarmonics) { - KtxBundle ktx(2, 1, true); + Ktx1Bundle ktx(2, 1, true); const char* sphereHarmonics = R"(0.199599 0.197587 0.208682 0.0894955 0.126985 0.187462 diff --git a/libs/ktxreader/CMakeLists.txt b/libs/ktxreader/CMakeLists.txt new file mode 100644 index 0000000000..6036c90a12 --- /dev/null +++ b/libs/ktxreader/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.19) +project(ktxreader) + +set(TARGET ktxreader) +set(PUBLIC_HDR_DIR include) + +# ================================================================================================== +# Sources and headers +# ================================================================================================== +set(PUBLIC_HDRS + include/ktxreader/Ktx1Reader.h +) + +set(SRCS + src/Ktx1Reader.cpp +) + +# ================================================================================================== +# Include and target definitions +# ================================================================================================== +include_directories(${PUBLIC_HDR_DIR}) + +add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS}) + +target_link_libraries(${TARGET} PUBLIC utils image filament) + +target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) + +# ================================================================================================== +# Compiler flags +# ================================================================================================== +if (MSVC) + target_compile_options(${TARGET} PRIVATE $<$:/fp:fast>) +else() + target_compile_options(${TARGET} PRIVATE $<$:-ffast-math>) + target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register) +endif() + +# ================================================================================================== +# Installation +# ================================================================================================== +install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR}) +install(DIRECTORY ${PUBLIC_HDR_DIR}/${TARGET} DESTINATION include) + +# ================================================================================================== +# Tests +# ================================================================================================== + +function(add_testfile filename) + set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/tests/${filename}") + set(target_path "${PROJECT_BINARY_DIR}/${filename}") + set(TESTFILES ${TESTFILES} ${target_path} PARENT_SCOPE) + add_custom_command( + OUTPUT ${target_path} + COMMAND ${CMAKE_COMMAND} -E copy ${source_path} ${target_path} + MAIN_DEPENDENCY ${source_path}) +endfunction() + +add_testfile(color_grid_uastc_zstd.ktx2) +add_testfile(lightroom_ibl.ktx) + +if (NOT ANDROID AND NOT WEBGL AND NOT IOS) + add_executable(test_ktxreader tests/test_ktxreader.cpp ${TESTFILES}) + target_link_libraries(test_ktxreader PRIVATE ${TARGET} gtest) +endif() diff --git a/libs/ktxreader/include/ktxreader/Ktx1Reader.h b/libs/ktxreader/include/ktxreader/Ktx1Reader.h new file mode 100644 index 0000000000..6cff1579f4 --- /dev/null +++ b/libs/ktxreader/include/ktxreader/Ktx1Reader.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMAGE_KTXUTILITY_H +#define IMAGE_KTXUTILITY_H + +#include +#include + +#include + +namespace ktxreader { + +using KtxInfo = image::KtxInfo; +using Ktx1Bundle = image::Ktx1Bundle; + +/** + * Allows clients to create Filament textures from Ktx1Bundle objects. + */ +namespace Ktx1Reader { + + using Texture = filament::Texture; + using Engine = filament::Engine; + + using TextureFormat = Texture::InternalFormat; + using CompressedPixelDataType = Texture::CompressedType; + using PixelDataType = Texture::Type; + using PixelDataFormat = Texture::Format; + using PixelBufferDescriptor = Texture::PixelBufferDescriptor; + + using Callback = void(*)(void* userdata); + + CompressedPixelDataType toCompressedPixelDataType(const KtxInfo& info); + PixelDataType toPixelDataType(const KtxInfo& info); + PixelDataFormat toPixelDataFormat(const KtxInfo& info); + bool isCompressed(const KtxInfo& info); + TextureFormat toTextureFormat(const KtxInfo& info); + TextureFormat toSrgbTextureFormat(TextureFormat tex); + + /** + * Creates a Texture object from a KTX file and populates all of its faces and miplevels. + * + * @param engine Used to create the Filament Texture + * @param ktx In-memory representation of a KTX file + * @param srgb Forces the KTX-specified format into an SRGB format if possible + * @param callback Gets called after all texture data has been uploaded to the GPU + * @param userdata Passed into the callback + */ + Texture* createTexture(Engine* engine, const Ktx1Bundle& ktx, bool srgb, + Callback callback, void* userdata); + + /** + * Creates a Texture object from a KTX bundle, populates all of its faces and miplevels, + * and automatically destroys the bundle after all the texture data has been uploaded. + * + * @param engine Used to create the Filament Texture + * @param ktx In-memory representation of a KTX file + * @param srgb Forces the KTX-specified format into an SRGB format if possible + */ + Texture* createTexture(Engine* engine, Ktx1Bundle* ktx, bool srgb); + + CompressedPixelDataType toCompressedPixelDataType(const KtxInfo& info); + + PixelDataType toPixelDataType(const KtxInfo& info); + + PixelDataFormat toPixelDataFormat(const KtxInfo& info); + + bool isCompressed(const KtxInfo& info); + + TextureFormat toSrgbTextureFormat(TextureFormat format); + + TextureFormat toTextureFormat(const KtxInfo& info); + +} // namespace Ktx1Reader +} // namespace ktxreader + +#endif diff --git a/libs/ktxreader/src/Ktx1Reader.cpp b/libs/ktxreader/src/Ktx1Reader.cpp new file mode 100644 index 0000000000..16d003207c --- /dev/null +++ b/libs/ktxreader/src/Ktx1Reader.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace ktxreader { +namespace Ktx1Reader { + +template +T toCompressedFilamentEnum(uint32_t format) { + switch (format) { + case Ktx1Bundle::RGB_S3TC_DXT1: return T::DXT1_RGB; + case Ktx1Bundle::RGBA_S3TC_DXT1: return T::DXT1_RGBA; + case Ktx1Bundle::RGBA_S3TC_DXT3: return T::DXT3_RGBA; + case Ktx1Bundle::RGBA_S3TC_DXT5: return T::DXT5_RGBA; + case Ktx1Bundle::RGBA_ASTC_4x4: return T::RGBA_ASTC_4x4; + case Ktx1Bundle::RGBA_ASTC_5x4: return T::RGBA_ASTC_5x4; + case Ktx1Bundle::RGBA_ASTC_5x5: return T::RGBA_ASTC_5x5; + case Ktx1Bundle::RGBA_ASTC_6x5: return T::RGBA_ASTC_6x5; + case Ktx1Bundle::RGBA_ASTC_6x6: return T::RGBA_ASTC_6x6; + case Ktx1Bundle::RGBA_ASTC_8x5: return T::RGBA_ASTC_8x5; + case Ktx1Bundle::RGBA_ASTC_8x6: return T::RGBA_ASTC_8x6; + case Ktx1Bundle::RGBA_ASTC_8x8: return T::RGBA_ASTC_8x8; + case Ktx1Bundle::RGBA_ASTC_10x5: return T::RGBA_ASTC_10x5; + case Ktx1Bundle::RGBA_ASTC_10x6: return T::RGBA_ASTC_10x6; + case Ktx1Bundle::RGBA_ASTC_10x8: return T::RGBA_ASTC_10x8; + case Ktx1Bundle::RGBA_ASTC_10x10: return T::RGBA_ASTC_10x10; + case Ktx1Bundle::RGBA_ASTC_12x10: return T::RGBA_ASTC_12x10; + case Ktx1Bundle::RGBA_ASTC_12x12: return T::RGBA_ASTC_12x12; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_4x4: return T::SRGB8_ALPHA8_ASTC_4x4; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_5x4: return T::SRGB8_ALPHA8_ASTC_5x4; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_5x5: return T::SRGB8_ALPHA8_ASTC_5x5; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_6x5: return T::SRGB8_ALPHA8_ASTC_6x5; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_6x6: return T::SRGB8_ALPHA8_ASTC_6x6; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x5: return T::SRGB8_ALPHA8_ASTC_8x5; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x6: return T::SRGB8_ALPHA8_ASTC_8x6; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x8: return T::SRGB8_ALPHA8_ASTC_8x8; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x5: return T::SRGB8_ALPHA8_ASTC_10x5; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x6: return T::SRGB8_ALPHA8_ASTC_10x6; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x8: return T::SRGB8_ALPHA8_ASTC_10x8; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x10: return T::SRGB8_ALPHA8_ASTC_10x10; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_12x10: return T::SRGB8_ALPHA8_ASTC_12x10; + case Ktx1Bundle::SRGB8_ALPHA8_ASTC_12x12: return T::SRGB8_ALPHA8_ASTC_12x12; + case Ktx1Bundle::R11_EAC: return T::EAC_R11; + case Ktx1Bundle::SIGNED_R11_EAC: return T::EAC_R11_SIGNED; + case Ktx1Bundle::RG11_EAC: return T::EAC_RG11; + case Ktx1Bundle::SIGNED_RG11_EAC: return T::EAC_RG11_SIGNED; + case Ktx1Bundle::RGB8_ETC2: return T::ETC2_RGB8; + case Ktx1Bundle::SRGB8_ETC2: return T::ETC2_SRGB8; + case Ktx1Bundle::RGB8_ALPHA1_ETC2: return T::ETC2_RGB8_A1; + case Ktx1Bundle::SRGB8_ALPHA1_ETC: return T::ETC2_SRGB8_A1; + case Ktx1Bundle::RGBA8_ETC2_EAC: return T::ETC2_EAC_RGBA8; + case Ktx1Bundle::SRGB8_ALPHA8_ETC2_EAC: return T::ETC2_EAC_SRGBA8; + } + return (T) 0xffff; +} + +Texture* createTexture(Engine* engine, const Ktx1Bundle& ktx, bool srgb, + Callback callback, void* userdata) { + using Sampler = Texture::Sampler; + const auto& ktxinfo = ktx.getInfo(); + const uint32_t nmips = ktx.getNumMipLevels(); + const auto cdatatype = toCompressedPixelDataType(ktxinfo); + const auto datatype = toPixelDataType(ktxinfo); + const auto dataformat = toPixelDataFormat(ktxinfo); + + auto texformat = toTextureFormat(ktxinfo); + if (srgb) { + texformat = toSrgbTextureFormat(texformat); + } + + Texture* texture = Texture::Builder() + .width(ktxinfo.pixelWidth) + .height(ktxinfo.pixelHeight) + .levels(static_cast(nmips)) + .sampler(ktx.isCubemap() ? Sampler::SAMPLER_CUBEMAP : Sampler::SAMPLER_2D) + .format(texformat) + .build(*engine); + + struct Userdata { + uint32_t remainingBuffers; + Callback callback; + void* userdata; + }; + + Userdata* cbuser = new Userdata({nmips, callback, userdata}); + + PixelBufferDescriptor::Callback cb = [](void*, size_t, void* cbuserptr) { + Userdata* cbuser = (Userdata*) cbuserptr; + if (--cbuser->remainingBuffers == 0) { + if (cbuser->callback) { + cbuser->callback(cbuser->userdata); + } + delete cbuser; + } + }; + + uint8_t* data; + uint32_t size; + + if (isCompressed(ktxinfo)) { + if (ktx.isCubemap()) { + for (uint32_t level = 0; level < nmips; ++level) { + ktx.getBlob({level, 0, 0}, &data, &size); + PixelBufferDescriptor pbd(data, size * 6, cdatatype, size, cb, cbuser); + texture->setImage(*engine, level, std::move(pbd), Texture::FaceOffsets(size)); + } + return texture; + } + for (uint32_t level = 0; level < nmips; ++level) { + ktx.getBlob({level, 0, 0}, &data, &size); + PixelBufferDescriptor pbd(data, size, cdatatype, size, cb, cbuser); + texture->setImage(*engine, level, std::move(pbd)); + } + return texture; + } + + if (ktx.isCubemap()) { + for (uint32_t level = 0; level < nmips; ++level) { + ktx.getBlob({level, 0, 0}, &data, &size); + PixelBufferDescriptor pbd(data, size * 6, dataformat, datatype, cb, cbuser); + texture->setImage(*engine, level, std::move(pbd), Texture::FaceOffsets(size)); + } + return texture; + } + + for (uint32_t level = 0; level < nmips; ++level) { + ktx.getBlob({level, 0, 0}, &data, &size); + PixelBufferDescriptor pbd(data, size, dataformat, datatype, cb, cbuser); + texture->setImage(*engine, level, std::move(pbd)); + } + return texture; +} + +Texture* createTexture(Engine* engine, Ktx1Bundle* ktx, bool srgb) { + auto freeKtx = [] (void* userdata) { + Ktx1Bundle* ktx = (Ktx1Bundle*) userdata; + delete ktx; + }; + return createTexture(engine, *ktx, srgb, freeKtx, ktx); +} + +CompressedPixelDataType toCompressedPixelDataType(const KtxInfo& info) { + return toCompressedFilamentEnum(info.glInternalFormat); +} + +PixelDataType toPixelDataType(const KtxInfo& info) { + switch (info.glType) { + case Ktx1Bundle::UNSIGNED_BYTE: return PixelDataType::UBYTE; + case Ktx1Bundle::UNSIGNED_SHORT: return PixelDataType::USHORT; + case Ktx1Bundle::HALF_FLOAT: return PixelDataType::HALF; + case Ktx1Bundle::FLOAT: return PixelDataType::FLOAT; + case Ktx1Bundle::R11F_G11F_B10F: return PixelDataType::UINT_10F_11F_11F_REV; + } + return (PixelDataType) 0xff; +} + +PixelDataFormat toPixelDataFormat(const KtxInfo& info) { + switch (info.glFormat) { + case Ktx1Bundle::LUMINANCE: + case Ktx1Bundle::RED: return PixelDataFormat::R; + case Ktx1Bundle::RG: return PixelDataFormat::RG; + case Ktx1Bundle::RGB: return PixelDataFormat::RGB; + case Ktx1Bundle::RGBA: return PixelDataFormat::RGBA; + // glFormat should NOT be a sized format according to the spec + // however cmgen was generating incorrect files until after Filament 1.8.0 + // so we keep this line here to preserve compatibility with older assets + case Ktx1Bundle::R11F_G11F_B10F: return PixelDataFormat::RGB; + } + return (PixelDataFormat) 0xff; +} + +bool isCompressed(const KtxInfo& info) { + return info.glFormat == 0; +} + +TextureFormat toSrgbTextureFormat(TextureFormat format) { + switch(format) { + // Non-compressed + case Texture::InternalFormat::RGB8: + return Texture::InternalFormat::SRGB8; + case Texture::InternalFormat::RGBA8: + return Texture::InternalFormat::SRGB8_A8; + + // ASTC + case Texture::InternalFormat::RGBA_ASTC_4x4: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_4x4; + case Texture::InternalFormat::RGBA_ASTC_5x4: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_5x4; + case Texture::InternalFormat::RGBA_ASTC_5x5: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_5x5; + case Texture::InternalFormat::RGBA_ASTC_6x5: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_6x5; + case Texture::InternalFormat::RGBA_ASTC_6x6: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_6x6; + case Texture::InternalFormat::RGBA_ASTC_8x5: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_8x5; + case Texture::InternalFormat::RGBA_ASTC_8x6: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_8x6; + case Texture::InternalFormat::RGBA_ASTC_8x8: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_8x8; + case Texture::InternalFormat::RGBA_ASTC_10x5: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_10x5; + case Texture::InternalFormat::RGBA_ASTC_10x6: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_10x6; + case Texture::InternalFormat::RGBA_ASTC_10x8: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_10x8; + case Texture::InternalFormat::RGBA_ASTC_10x10: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_10x10; + case Texture::InternalFormat::RGBA_ASTC_12x10: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_12x10; + case Texture::InternalFormat::RGBA_ASTC_12x12: + return Texture::InternalFormat::SRGB8_ALPHA8_ASTC_12x12; + + // ETC2 + case Texture::InternalFormat::ETC2_RGB8: + return Texture::InternalFormat::ETC2_SRGB8; + case Texture::InternalFormat::ETC2_RGB8_A1: + return Texture::InternalFormat::ETC2_SRGB8_A1; + case Texture::InternalFormat::ETC2_EAC_RGBA8: + return Texture::InternalFormat::ETC2_EAC_SRGBA8; + + // DXT + case Texture::InternalFormat::DXT1_RGB: + return Texture::InternalFormat::DXT1_SRGB; + case Texture::InternalFormat::DXT1_RGBA: + return Texture::InternalFormat::DXT1_SRGBA; + case Texture::InternalFormat::DXT3_RGBA: + return Texture::InternalFormat::DXT3_SRGBA; + case Texture::InternalFormat::DXT5_RGBA: + return Texture::InternalFormat::DXT5_SRGBA; + + default: + return format; + } +} + +TextureFormat toTextureFormat(const KtxInfo& info) { + switch (info.glInternalFormat) { + case Ktx1Bundle::RED: return TextureFormat::R8; + case Ktx1Bundle::RG: return TextureFormat::RG8; + case Ktx1Bundle::RGB: return TextureFormat::RGB8; + case Ktx1Bundle::RGBA: return TextureFormat::RGBA8; + case Ktx1Bundle::LUMINANCE: return TextureFormat::R8; + case Ktx1Bundle::LUMINANCE_ALPHA: return TextureFormat::RG8; + case Ktx1Bundle::R8: return TextureFormat::R8; + case Ktx1Bundle::R8_SNORM: return TextureFormat::R8_SNORM; + case Ktx1Bundle::R8UI: return TextureFormat::R8UI; + case Ktx1Bundle::R8I: return TextureFormat::R8I; + case Ktx1Bundle::STENCIL_INDEX8: return TextureFormat::STENCIL8; + case Ktx1Bundle::R16F: return TextureFormat::R16F; + case Ktx1Bundle::R16UI: return TextureFormat::R16UI; + case Ktx1Bundle::R16I: return TextureFormat::R16I; + case Ktx1Bundle::RG8: return TextureFormat::RG8; + case Ktx1Bundle::RG8_SNORM: return TextureFormat::RG8_SNORM; + case Ktx1Bundle::RG8UI: return TextureFormat::RG8UI; + case Ktx1Bundle::RG8I: return TextureFormat::RG8I; + case Ktx1Bundle::RGB565: return TextureFormat::RGB565; + case Ktx1Bundle::RGB9_E5: return TextureFormat::RGB9_E5; + case Ktx1Bundle::RGB5_A1: return TextureFormat::RGB5_A1; + case Ktx1Bundle::RGBA4: return TextureFormat::RGBA4; + case Ktx1Bundle::DEPTH_COMPONENT16: return TextureFormat::DEPTH16; + case Ktx1Bundle::RGB8: return TextureFormat::RGB8; + case Ktx1Bundle::SRGB8: return TextureFormat::SRGB8; + case Ktx1Bundle::RGB8_SNORM: return TextureFormat::RGB8_SNORM; + case Ktx1Bundle::RGB8UI: return TextureFormat::RGB8UI; + case Ktx1Bundle::RGB8I: return TextureFormat::RGB8I; + case Ktx1Bundle::R32F: return TextureFormat::R32F; + case Ktx1Bundle::R32UI: return TextureFormat::R32UI; + case Ktx1Bundle::R32I: return TextureFormat::R32I; + case Ktx1Bundle::RG16F: return TextureFormat::RG16F; + case Ktx1Bundle::RG16UI: return TextureFormat::RG16UI; + case Ktx1Bundle::RG16I: return TextureFormat::RG16I; + case Ktx1Bundle::R11F_G11F_B10F: return TextureFormat::R11F_G11F_B10F; + case Ktx1Bundle::RGBA8: return TextureFormat::RGBA8; + case Ktx1Bundle::SRGB8_ALPHA8: return TextureFormat::SRGB8_A8; + case Ktx1Bundle::RGBA8_SNORM: return TextureFormat::RGBA8_SNORM; + case Ktx1Bundle::RGB10_A2: return TextureFormat::RGB10_A2; + case Ktx1Bundle::RGBA8UI: return TextureFormat::RGBA8UI; + case Ktx1Bundle::RGBA8I: return TextureFormat::RGBA8I; + case Ktx1Bundle::DEPTH24_STENCIL8: return TextureFormat::DEPTH24_STENCIL8; + case Ktx1Bundle::DEPTH32F_STENCIL8: return TextureFormat::DEPTH32F_STENCIL8; + case Ktx1Bundle::RGB16F: return TextureFormat::RGB16F; + case Ktx1Bundle::RGB16UI: return TextureFormat::RGB16UI; + case Ktx1Bundle::RGB16I: return TextureFormat::RGB16I; + case Ktx1Bundle::RG32F: return TextureFormat::RG32F; + case Ktx1Bundle::RG32UI: return TextureFormat::RG32UI; + case Ktx1Bundle::RG32I: return TextureFormat::RG32I; + case Ktx1Bundle::RGBA16F: return TextureFormat::RGBA16F; + case Ktx1Bundle::RGBA16UI: return TextureFormat::RGBA16UI; + case Ktx1Bundle::RGBA16I: return TextureFormat::RGBA16I; + case Ktx1Bundle::RGB32F: return TextureFormat::RGB32F; + case Ktx1Bundle::RGB32UI: return TextureFormat::RGB32UI; + case Ktx1Bundle::RGB32I: return TextureFormat::RGB32I; + case Ktx1Bundle::RGBA32F: return TextureFormat::RGBA32F; + case Ktx1Bundle::RGBA32UI: return TextureFormat::RGBA32UI; + case Ktx1Bundle::RGBA32I: return TextureFormat::RGBA32I; + } + return toCompressedFilamentEnum(info.glInternalFormat); +} + +} // namespace Ktx1Reader +} // namespace ktxreader diff --git a/libs/ktxreader/tests/color_grid_uastc_zstd.ktx2 b/libs/ktxreader/tests/color_grid_uastc_zstd.ktx2 new file mode 100644 index 0000000000..b1ca0735c3 Binary files /dev/null and b/libs/ktxreader/tests/color_grid_uastc_zstd.ktx2 differ diff --git a/libs/ktxreader/tests/lightroom_ibl.ktx b/libs/ktxreader/tests/lightroom_ibl.ktx new file mode 100644 index 0000000000..e8b1ba4b00 Binary files /dev/null and b/libs/ktxreader/tests/lightroom_ibl.ktx differ diff --git a/libs/ktxreader/tests/test_ktxreader.cpp b/libs/ktxreader/tests/test_ktxreader.cpp new file mode 100644 index 0000000000..f66a961bd0 --- /dev/null +++ b/libs/ktxreader/tests/test_ktxreader.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include + +#include +#include + +using namespace filament; +using namespace std; + +using utils::Path; + +class KtxReaderTest : public testing::Test { +protected: + void SetUp() override { + engine = Engine::create(Engine::Backend::NOOP); + } + + void TearDown() override { + Engine::destroy(&engine); + } + + Engine* engine = nullptr; +}; + +static vector readFile(const Path& inputPath) { + std::ifstream file(inputPath, ios::binary); + return vector((istreambuf_iterator(file)), {}); +} + +TEST_F(KtxReaderTest, Ktx1) { + const utils::Path parent = Path::getCurrentExecutable().getParent(); + const auto contents = readFile(parent + "lightroom_ibl.ktx"); + ASSERT_EQ(contents.size(), 129368); + + image::Ktx1Bundle bundle(contents.data(), contents.size()); + + Texture* tex = ktxreader::Ktx1Reader::createTexture(engine, bundle, false, + [](void* userdata) { /* */ }, nullptr); + + ASSERT_TRUE(tex != nullptr); + + ASSERT_EQ(tex->getWidth(), 64); + ASSERT_EQ(tex->getHeight(), 64); + + engine->destroy(tex); +} + +TEST_F(KtxReaderTest, Ktx2) { + const utils::Path parent = Path::getCurrentExecutable().getParent(); + const auto contents = readFile(parent + "color_grid_uastc_zstd.ktx2"); + ASSERT_EQ(contents.size(), 170512); + + // TODO: create Filament texture from the KTX2 file. +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/samples/suzanne.cpp b/samples/suzanne.cpp index 1eb07fafc0..b7a44bc878 100644 --- a/samples/suzanne.cpp +++ b/samples/suzanne.cpp @@ -28,8 +28,7 @@ #include -#include -#include +#include #include #include @@ -47,7 +46,7 @@ #include "generated/resources/monkey.h" using namespace filament; -using namespace image; +using namespace ktxreader; using namespace filament::math; struct App { @@ -146,14 +145,14 @@ int main(int argc, char** argv) { auto& em = utils::EntityManager::get(); // Create textures. The KTX bundles are freed by KtxUtility. - auto albedo = new image::KtxBundle(MONKEY_ALBEDO_S3TC_DATA, MONKEY_ALBEDO_S3TC_SIZE); - auto ao = new image::KtxBundle(MONKEY_AO_DATA, MONKEY_AO_SIZE); - auto metallic = new image::KtxBundle(MONKEY_METALLIC_DATA, MONKEY_METALLIC_SIZE); - auto roughness = new image::KtxBundle(MONKEY_ROUGHNESS_DATA, MONKEY_ROUGHNESS_SIZE); - app.albedo = ktx::createTexture(engine, albedo, true); - app.ao = ktx::createTexture(engine, ao, false); - app.metallic = ktx::createTexture(engine, metallic, false); - app.roughness = ktx::createTexture(engine, roughness, false); + auto albedo = new image::Ktx1Bundle(MONKEY_ALBEDO_S3TC_DATA, MONKEY_ALBEDO_S3TC_SIZE); + auto ao = new image::Ktx1Bundle(MONKEY_AO_DATA, MONKEY_AO_SIZE); + auto metallic = new image::Ktx1Bundle(MONKEY_METALLIC_DATA, MONKEY_METALLIC_SIZE); + auto roughness = new image::Ktx1Bundle(MONKEY_ROUGHNESS_DATA, MONKEY_ROUGHNESS_SIZE); + app.albedo = Ktx1Reader::createTexture(engine, albedo, true); + app.ao = Ktx1Reader::createTexture(engine, ao, false); + app.metallic = Ktx1Reader::createTexture(engine, metallic, false); + app.roughness = Ktx1Reader::createTexture(engine, roughness, false); app.normal = loadNormalMap(engine, MONKEY_NORMAL_DATA, MONKEY_NORMAL_SIZE); TextureSampler sampler(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR, TextureSampler::MagFilter::LINEAR); diff --git a/tools/cmgen/src/cmgen.cpp b/tools/cmgen/src/cmgen.cpp index 2035135ad0..5e67779abe 100644 --- a/tools/cmgen/src/cmgen.cpp +++ b/tools/cmgen/src/cmgen.cpp @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include @@ -137,7 +137,7 @@ static void UTILS_UNUSED outputSpectrum(std::ostream& out, static void saveImage(const std::string& path, ImageEncoder::Format format, const Image& image, const std::string& compression); static LinearImage toLinearImage(const Image& image); -static void exportKtxFaces(KtxBundle& container, uint32_t miplevel, const Cubemap& cm); +static void exportKtxFaces(Ktx1Bundle& container, uint32_t miplevel, const Cubemap& cm); // ----------------------------------------------------------------------------------------------- @@ -936,14 +936,14 @@ void iblRoughnessPrefilter( // It's convenient to create an empty KTX bundle on the stack in this scope, regardless of // whether KTX is requested. It does not consume memory if empty. - KtxBundle container((uint32_t) numLevels, 1, true); + Ktx1Bundle container((uint32_t) numLevels, 1, true); container.info() = { - .endianness = KtxBundle::ENDIAN_DEFAULT, - .glType = KtxBundle::R11F_G11F_B10F, + .endianness = Ktx1Bundle::ENDIAN_DEFAULT, + .glType = Ktx1Bundle::R11F_G11F_B10F, .glTypeSize = 1, - .glFormat = KtxBundle::RGB, - .glInternalFormat = KtxBundle::R11F_G11F_B10F, - .glBaseInternalFormat = KtxBundle::R11F_G11F_B10F, + .glFormat = Ktx1Bundle::RGB, + .glInternalFormat = Ktx1Bundle::R11F_G11F_B10F, + .glBaseInternalFormat = Ktx1Bundle::R11F_G11F_B10F, .pixelWidth = 1U << baseExp, .pixelHeight = 1U << baseExp, .pixelDepth = 0, @@ -1195,14 +1195,14 @@ void extractCubemapFaces(utils::JobSystem& js, const utils::Path& iname, const C if (g_type == OutputType::KTX) { const uint32_t dim = (const uint32_t) cm.getDimensions(); - KtxBundle container(1, 1, true); + Ktx1Bundle container(1, 1, true); container.info() = { - .endianness = KtxBundle::ENDIAN_DEFAULT, - .glType = KtxBundle::R11F_G11F_B10F, + .endianness = Ktx1Bundle::ENDIAN_DEFAULT, + .glType = Ktx1Bundle::R11F_G11F_B10F, .glTypeSize = 1, - .glFormat = KtxBundle::RGB, - .glInternalFormat = KtxBundle::R11F_G11F_B10F, - .glBaseInternalFormat = KtxBundle::R11F_G11F_B10F, + .glFormat = Ktx1Bundle::RGB, + .glInternalFormat = Ktx1Bundle::R11F_G11F_B10F, + .glBaseInternalFormat = Ktx1Bundle::R11F_G11F_B10F, .pixelWidth = dim, .pixelHeight = dim, .pixelDepth = 0, @@ -1268,7 +1268,7 @@ static void saveImage(const std::string& path, ImageEncoder::Format format, cons } } -static void exportKtxFaces(KtxBundle& container, uint32_t miplevel, const Cubemap& cm) { +static void exportKtxFaces(Ktx1Bundle& container, uint32_t miplevel, const Cubemap& cm) { auto& info = container.info(); #ifdef IMAGEIO_SUPPORTS_BLOCK_COMPRESSION @@ -1285,8 +1285,8 @@ static void exportKtxFaces(KtxBundle& container, uint32_t miplevel, const Cubema info.glTypeSize = 1; info.glFormat = 0; // FIXME: not sure this is always correct to use RGB here, does this work with HDR formats? - info.glBaseInternalFormat = KtxBundle::RGB; - info.glInternalFormat = KtxBundle::RGB; + info.glBaseInternalFormat = Ktx1Bundle::RGB; + info.glInternalFormat = Ktx1Bundle::RGB; } #else if (!g_compression.empty()) { diff --git a/tools/mipgen/src/main.cpp b/tools/mipgen/src/main.cpp index 17e608a9a1..493c7d6ebd 100644 --- a/tools/mipgen/src/main.cpp +++ b/tools/mipgen/src/main.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #ifdef IMAGEIO_SUPPORTS_BLOCK_COMPRESSION @@ -326,11 +326,11 @@ int main(int argc, char* argv[]) { // The libimage API does not include the original image in the mip array, // which might make sense when generating individual files, but for a KTX // bundle, we want to include level 0, so add 1 to the KTX level count. - KtxBundle container(1 + miplevels.size(), 1, false); + Ktx1Bundle container(1 + miplevels.size(), 1, false); auto& info = container.info(); info = { - .endianness = KtxBundle::ENDIAN_DEFAULT, - .glType = KtxBundle::UNSIGNED_BYTE, + .endianness = Ktx1Bundle::ENDIAN_DEFAULT, + .glType = Ktx1Bundle::UNSIGNED_BYTE, .glTypeSize = 1, .pixelWidth = sourceImage.getWidth(), .pixelHeight = sourceImage.getHeight(), @@ -338,14 +338,14 @@ int main(int argc, char* argv[]) { }; size_t componentCount = sourceImage.getChannels(); if (componentCount == 1) { - info.glFormat = info.glBaseInternalFormat = KtxBundle::RED; - info.glInternalFormat = KtxBundle::R8; + info.glFormat = info.glBaseInternalFormat = Ktx1Bundle::RED; + info.glInternalFormat = Ktx1Bundle::R8; } else if (componentCount == 3) { - info.glFormat = info.glBaseInternalFormat = KtxBundle::RGB; - info.glInternalFormat = KtxBundle::RGB8; + info.glFormat = info.glBaseInternalFormat = Ktx1Bundle::RGB; + info.glInternalFormat = Ktx1Bundle::RGB8; } else if (componentCount == 4) { - info.glFormat = info.glBaseInternalFormat = KtxBundle::RGBA; - info.glInternalFormat = KtxBundle::RGBA8; + info.glFormat = info.glBaseInternalFormat = Ktx1Bundle::RGBA; + info.glInternalFormat = Ktx1Bundle::RGBA8; } #ifdef IMAGEIO_SUPPORTS_BLOCK_COMPRESSION CompressionConfig config {}; diff --git a/tools/roughness-prefilter/src/main.cpp b/tools/roughness-prefilter/src/main.cpp index a56a8b9ab9..e6ed1d74b8 100644 --- a/tools/roughness-prefilter/src/main.cpp +++ b/tools/roughness-prefilter/src/main.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include @@ -364,15 +364,15 @@ int main(int argc, char* argv[]) { js.adopt(); // For thread safety, we allocate each KTX blob now, before invoking the job system. - image::KtxBundle bundle(mipLevels, 1, false); + image::Ktx1Bundle bundle(mipLevels, 1, false); if (g_ktxContainer) { bundle.info() = { - .endianness = KtxBundle::ENDIAN_DEFAULT, - .glType = KtxBundle::UNSIGNED_BYTE, + .endianness = Ktx1Bundle::ENDIAN_DEFAULT, + .glType = Ktx1Bundle::UNSIGNED_BYTE, .glTypeSize = 1, - .glFormat = KtxBundle::LUMINANCE, - .glInternalFormat = KtxBundle::LUMINANCE, - .glBaseInternalFormat = KtxBundle::LUMINANCE, + .glFormat = Ktx1Bundle::LUMINANCE, + .glInternalFormat = Ktx1Bundle::LUMINANCE, + .glBaseInternalFormat = Ktx1Bundle::LUMINANCE, .pixelWidth = (uint32_t) width, .pixelHeight = (uint32_t) height, .pixelDepth = 0, diff --git a/web/docs/tutorial_redball.md b/web/docs/tutorial_redball.md index 079c65f330..c55542b62d 100644 --- a/web/docs/tutorial_redball.md +++ b/web/docs/tutorial_redball.md @@ -236,7 +236,7 @@ const datatype = Filament.PixelDataType.UINT_10F_11F_11F_REV; // Create a Texture object for the mipmapped cubemap. const ibl_package = Filament.Buffer(Filament.assets[ibl_url]); -const iblktx = new Filament.KtxBundle(ibl_package); +const iblktx = new Filament.Ktx1Bundle(ibl_package); const ibltex = Filament.Texture.Builder() .width(iblktx.info().pixelWidth) @@ -283,7 +283,7 @@ Here's one way to create a texture for the skybox: ```js const sky_package = Filament.Buffer(Filament.assets[sky_url]); -const skyktx = new Filament.KtxBundle(sky_package); +const skyktx = new Filament.Ktx1Bundle(sky_package); const skytex = Filament.Texture.Builder() .width(skyktx.info().pixelWidth) .height(skyktx.info().pixelHeight) diff --git a/web/filament-js/CMakeLists.txt b/web/filament-js/CMakeLists.txt index 1761ffb390..9ecb9fe128 100644 --- a/web/filament-js/CMakeLists.txt +++ b/web/filament-js/CMakeLists.txt @@ -41,7 +41,7 @@ set_target_properties(filament-js PROPERTIES LINK_DEPENDS "${EXTERN_POSTJS_SRC}" OUTPUT_NAME filament) -target_link_libraries(filament-js PRIVATE filament math utils image filameshio gltfio_core viewer) +target_link_libraries(filament-js PRIVATE filament math utils ktxreader filameshio gltfio_core viewer) # Copy package.json (et al) into the output folder to allow "npm publish" to gather the # artifacts that it needs. diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index bc1d950f0f..f4baec28fc 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -437,14 +437,14 @@ Filament.loadClassExtensions = function() { return result; }; - Filament.KtxBundle.prototype.getBlob = function(index) { + Filament.Ktx1Bundle.prototype.getBlob = function(index) { const blob = this._getBlob(index); const result = blob.getBytes(); blob.delete(); return result; } - Filament.KtxBundle.prototype.getCubeBlob = function(miplevel) { + Filament.Ktx1Bundle.prototype.getCubeBlob = function(miplevel) { const blob = this._getCubeBlob(miplevel); const result = blob.getBytes(); blob.delete(); diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 2eec57ce16..ce3a952436 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -68,8 +68,7 @@ #include #include -#include -#include +#include #include #include @@ -93,6 +92,7 @@ using namespace filamesh; using namespace geometry; using namespace gltfio; using namespace image; +using namespace ktxreader; using namespace filament::viewer; @@ -1525,29 +1525,29 @@ class_("driver$PixelBufferDescriptor") // HELPER TYPES // ------------ -/// KtxBundle ::class:: In-memory representation of a KTX file. +/// Ktx1Bundle ::class:: In-memory representation of a KTX file. /// Most clients should use one of the `create*FromKtx` utility methods in the JavaScript [Engine] -/// wrapper rather than interacting with `KtxBundle` directly. -class_("KtxBundle") - .constructor(EMBIND_LAMBDA(KtxBundle*, (BufferDescriptor kbd), { - return new KtxBundle((uint8_t*) kbd.bd->buffer, (uint32_t) kbd.bd->size); +/// wrapper rather than interacting with `Ktx1Bundle` directly. +class_("Ktx1Bundle") + .constructor(EMBIND_LAMBDA(Ktx1Bundle*, (BufferDescriptor kbd), { + return new Ktx1Bundle((uint8_t*) kbd.bd->buffer, (uint32_t) kbd.bd->size); })) /// info ::method:: Obtains properties of the KTX header. /// ::retval:: The [KtxInfo] property accessor object. - .function("getNumMipLevels", &KtxBundle::getNumMipLevels) + .function("getNumMipLevels", &Ktx1Bundle::getNumMipLevels) /// getArrayLength ::method:: Obtains length of the texture array. /// ::retval:: The number of elements in the texture array - .function("getArrayLength", &KtxBundle::getArrayLength) + .function("getArrayLength", &Ktx1Bundle::getArrayLength) /// getInternalFormat ::method:: /// srgb ::argument:: boolean that forces the resulting format to SRGB if possible. /// ::retval:: [Texture$InternalFormat] /// Returns "undefined" if no valid Filament enumerant exists. .function("getInternalFormat", - EMBIND_LAMBDA(Texture::InternalFormat, (KtxBundle* self, bool srgb), { - auto result = ktx::toTextureFormat(self->info()); + EMBIND_LAMBDA(Texture::InternalFormat, (Ktx1Bundle* self, bool srgb), { + auto result = Ktx1Reader::toTextureFormat(self->info()); if (srgb) { if (result == Texture::InternalFormat::RGB8) { result = Texture::InternalFormat::SRGB8; @@ -1563,42 +1563,42 @@ class_("KtxBundle") /// ::retval:: [PixelDataFormat] /// Returns "undefined" if no valid Filament enumerant exists. .function("getPixelDataFormat", - EMBIND_LAMBDA(backend::PixelDataFormat, (KtxBundle* self), { - return ktx::toPixelDataFormat(self->getInfo()); + EMBIND_LAMBDA(backend::PixelDataFormat, (Ktx1Bundle* self), { + return Ktx1Reader::toPixelDataFormat(self->getInfo()); }), allow_raw_pointers()) /// getPixelDataType ::method:: /// ::retval:: [PixelDataType] /// Returns "undefined" if no valid Filament enumerant exists. .function("getPixelDataType", - EMBIND_LAMBDA(backend::PixelDataType, (KtxBundle* self), { - return ktx::toPixelDataType(self->getInfo()); + EMBIND_LAMBDA(backend::PixelDataType, (Ktx1Bundle* self), { + return Ktx1Reader::toPixelDataType(self->getInfo()); }), allow_raw_pointers()) /// getCompressedPixelDataType ::method:: /// ::retval:: [CompressedPixelDataType] /// Returns "undefined" if no valid Filament enumerant exists. .function("getCompressedPixelDataType", - EMBIND_LAMBDA(backend::CompressedPixelDataType, (KtxBundle* self), { - return ktx::toCompressedPixelDataType(self->getInfo()); + EMBIND_LAMBDA(backend::CompressedPixelDataType, (Ktx1Bundle* self), { + return Ktx1Reader::toCompressedPixelDataType(self->getInfo()); }), allow_raw_pointers()) /// isCompressed ::method:: /// Per spec, compressed textures in KTX always have their glFormat field set to 0. /// ::retval:: boolean - .function("isCompressed", EMBIND_LAMBDA(bool, (KtxBundle* self), { - return ktx::isCompressed(self->getInfo()); + .function("isCompressed", EMBIND_LAMBDA(bool, (Ktx1Bundle* self), { + return Ktx1Reader::isCompressed(self->getInfo()); }), allow_raw_pointers()) - .function("isCubemap", &KtxBundle::isCubemap) - .function("_getBlob", EMBIND_LAMBDA(BufferDescriptor, (KtxBundle* self, KtxBlobIndex index), { + .function("isCubemap", &Ktx1Bundle::isCubemap) + .function("_getBlob", EMBIND_LAMBDA(BufferDescriptor, (Ktx1Bundle* self, KtxBlobIndex index), { uint8_t* data; uint32_t size; self->getBlob(index, &data, &size); return BufferDescriptor(data, size); }), allow_raw_pointers()) .function("_getCubeBlob", EMBIND_LAMBDA(BufferDescriptor, - (KtxBundle* self, uint32_t miplevel), { + (Ktx1Bundle* self, uint32_t miplevel), { uint8_t* data; uint32_t size; self->getBlob({miplevel}, &data, &size); @@ -1607,22 +1607,22 @@ class_("KtxBundle") /// info ::method:: Obtains properties of the KTX header. /// ::retval:: The [KtxInfo] property accessor object. - .function("info", &KtxBundle::info) + .function("info", &Ktx1Bundle::info) /// getMetadata ::method:: Obtains arbitrary metadata from the KTX file. /// key ::argument:: string /// ::retval:: string - .function("getMetadata", EMBIND_LAMBDA(std::string, (KtxBundle* self, std::string key), { + .function("getMetadata", EMBIND_LAMBDA(std::string, (Ktx1Bundle* self, std::string key), { return std::string(self->getMetadata(key.c_str())); }), allow_raw_pointers()); function("ktx$createTexture", EMBIND_LAMBDA(Texture*, - (Engine* engine, const KtxBundle& ktx, bool srgb), { - return ktx::createTexture(engine, ktx, srgb, nullptr, nullptr); + (Engine* engine, const Ktx1Bundle& ktx, bool srgb), { + return Ktx1Reader::createTexture(engine, ktx, srgb, nullptr, nullptr); }), allow_raw_pointers()); /// KtxInfo ::class:: Property accessor for KTX header. -/// For example, `ktxbundle.info().pixelWidth`. See the +/// For example, `Ktx1Bundle.info().pixelWidth`. See the /// [KTX spec](https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/) for the list of /// properties. class_("KtxInfo") diff --git a/web/filament-js/utilities.js b/web/filament-js/utilities.js index c400358472..0c639b6429 100644 --- a/web/filament-js/utilities.js +++ b/web/filament-js/utilities.js @@ -242,14 +242,14 @@ Filament.loadMathExtensions = function() { Filament._createTextureFromKtx = function(ktxdata, engine, options) { options = options || {}; - const ktx = options['ktx'] || new Filament.KtxBundle(ktxdata); + const ktx = options['ktx'] || new Filament.Ktx1Bundle(ktxdata); const srgb = !!options['srgb']; return Filament.ktx$createTexture(engine, ktx, srgb); }; Filament._createIblFromKtx = function(ktxdata, engine, options) { options = options || {}; - const iblktx = options['ktx'] = new Filament.KtxBundle(ktxdata); + const iblktx = options['ktx'] = new Filament.Ktx1Bundle(ktxdata); const format = iblktx.info().glInternalFormat; //if (format != this.ctx.R11F_G11F_B10F && format != this.ctx.RGB16F && format != this.ctx.RGB32F) {