diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/KTX1Loader.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/KTX1Loader.kt new file mode 100644 index 0000000000..d221e59cc6 --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/KTX1Loader.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 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. + */ + +package com.google.android.filament.utils + +import com.google.android.filament.Engine +import com.google.android.filament.IndirectLight +import com.google.android.filament.Skybox +import com.google.android.filament.Texture + +import java.nio.Buffer + +/** + * Utilities for consuming KTX1 files and producing Filament textures, IBLs, and sky boxes. + * + * KTX is a simple container format that makes it easy to bundle miplevels and cubemap faces + * into a single file. + */ +object KTX1Loader { + class Options { + var srgb = false + } + + /** + * Consumes the content of a KTX file and produces a [Texture] object. + * + * @param engine Gets passed to the builder. + * @param buffer The content of the KTX File. + * @param options Loader options. + * @return The resulting Filament texture, or null on failure. + */ + fun createTexture(engine: Engine, buffer: Buffer, options: Options = Options()): Texture { + val nativeEngine = engine.nativeObject + val nativeTexture = nCreateKTXTexture(nativeEngine, buffer, buffer.remaining(), options.srgb) + return Texture(nativeTexture) + } + + /** + * Consumes the content of a KTX file and produces an [IndirectLight] object. + * + * @param engine Gets passed to the builder. + * @param buffer The content of the KTX File. + * @param options Loader options. + * @return The resulting Filament texture, or null on failure. + */ + fun createIndirectLight(engine: Engine, buffer: Buffer, options: Options = Options()): IndirectLight { + val nativeEngine = engine.nativeObject + val nativeIndirectLight = nCreateIndirectLight(nativeEngine, buffer, buffer.remaining(), options.srgb) + return IndirectLight(nativeIndirectLight) + } + + /** + * Consumes the content of a KTX file and produces a [Skybox] object. + * + * @param engine Gets passed to the builder. + * @param buffer The content of the KTX File. + * @param options Loader options. + * @return The resulting Filament texture, or null on failure. + */ + fun createSkybox(engine: Engine, buffer: Buffer, options: Options = Options()): Skybox { + val nativeEngine = engine.nativeObject + val nativeSkybox = nCreateSkybox(nativeEngine, buffer, buffer.remaining(), options.srgb) + return Skybox(nativeSkybox) + } + + /** + * Retrieves spherical harmonics from the content of a KTX file. + * + * @param buffer The content of the KTX File. + * @return The resulting array of 9 * 3 floats, or null on failure. + */ + fun getSphericalHarmonics(buffer: Buffer): FloatArray? { + val sphericalHarmonics = FloatArray(9 * 3) + val success = nGetSphericalHarmonics(buffer, buffer.remaining(), sphericalHarmonics) + return if (success) sphericalHarmonics else null + } + + private external fun nCreateKTXTexture(nativeEngine: Long, buffer: Buffer, remaining: Int, srgb: Boolean): Long + private external fun nCreateIndirectLight(nativeEngine: Long, buffer: Buffer, remaining: Int, srgb: Boolean): Long + private external fun nGetSphericalHarmonics(buffer: Buffer, remaining: Int, outSphericalHarmonics: FloatArray): Boolean + private external fun nCreateSkybox(nativeEngine: Long, buffer: Buffer, remaining: Int, srgb: Boolean): Long +} \ No newline at end of file diff --git a/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm b/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm index 0da5c269e8..e2dd92e992 100644 --- a/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm +++ b/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm @@ -18,6 +18,7 @@ #import "FILModelView.h" +#include #include #include diff --git a/libs/imageio/include/imageio/BasisEncoder.h b/libs/imageio/include/imageio/BasisEncoder.h index b72385f9a4..cc1f14c873 100644 --- a/libs/imageio/include/imageio/BasisEncoder.h +++ b/libs/imageio/include/imageio/BasisEncoder.h @@ -30,69 +30,97 @@ struct BasisEncoderImpl; class UTILS_PUBLIC BasisEncoder { public: - enum IntermediateFormat { + enum class IntermediateFormat { UASTC, ETC1S, }; class Builder { public: - // The Ktx2 builder is constructed with a fixed number of miplevels and layers so - // that it can pre-allocate the appropriate BasisU input vectors. - // - // - mipCount: number of mipmap levels, including the base; must be at least 1. - // - layerCount: either 1 or the number of layers in an array texture. - // - // For cubemaps and cubemap arrays, multiply the layer count by 6 and pack the faces in - // standard GL order. + /** + * Constructs a Ktx2 builder with a fixed number of miplevels and layers. + * + * The number of mips and layers is required up front to allow pre-allocation of the + * appropriate BasisU input vectors. + * + * @param mipCount number of mipmap levels, including the base; must be at least 1. + * @param layerCount either 1 or the number of layers in an array texture. + * + * For cubemaps and cubemap arrays, multiply the layer count by 6 and pack the faces in + * standard GL order. + */ Builder(size_t mipCount, size_t layerCount) noexcept; ~Builder() noexcept; Builder(Builder&& that) noexcept; Builder& operator=(Builder&& that) noexcept; - // Enables the linear flag, which does two things: - // (1) Specifies that the image should be encoded without a transfer function. - // (2) Adds a tag to the ktx file that tells the loader that no transfer function was used. - // - // Note that the tag does not actually affect the compression process, it's basically just a - // hint to the reader. At the time of this writing, BasisU does not make a distinction - // between sRGB targets and linear targets. - // - // default value: FALSE + /** + * Enables the linear flag. (default value: FALSE) + * + * This does two things: + * (1) Specifies that the image should be encoded without a transfer function. + * (2) Adds a tag to the ktx file that tells the loader that no transfer function was used. + * + * Note that the tag does not actually affect the compression process, it's basically just a + * hint to the reader. At the time of this writing, BasisU does not make a distinction + * between sRGB targets and linear targets. + */ Builder& linear(bool enabled) noexcept; - // Enables cubemap (or cubemap array) mode. When this is enabled the number of layers - // should be divisible by 6. - // default value: FALSE + /** + * Enables cubemap or cubemap array mode. (default value: FALSE) + * + * When this is enabled the number of layers should be divisible by 6. + */ Builder& cubemap(bool enabled) noexcept; - // Chooses the intermiediate format as described in the BasisU documentation. - // For highest quality, use UASTC. - // default value: UASTC + /** + * Chooses the intermediate format as described in the BasisU docs. (default value: UASTC) + * + * For highest quality, use UASTC. + */ Builder& intermediateFormat(IntermediateFormat format) noexcept; - // Honors only the first component of the incoming LinearImage. - // default value: FALSE + /** + * Specifies that only the first component of the incoming LinearImage should be honored. + * + * default value: FALSE + */ Builder& grayscale(bool enabled) noexcept; - // Transforms the incoming image from [-1, +1] to [0, 1] before passing it to the encoder. - // default value: FALSE + /** + * Specifies that the incoming image should be transfored from [-1, +1] to [0, 1] before it + * passed to the Basis encoder. + * + * default value: FALSE + */ Builder& normals(bool enabled) noexcept; - // Initializes the basis encoder with the given number of jobs. - // default value: 4 + /** + * Initializes the basis encoder with the given number of jobs. + * + * default value: 4 + */ Builder& jobs(size_t count) noexcept; - // Supresses status messages. - // default value: FALSE + /** + * Supresses status messages. + * + * default value: FALSE + */ Builder& quiet(bool enabled) noexcept; - // Submits image data in linear floating-point format. - // This must be called for every miplevel. + /** + * Submits image data in linear floating-point format. + * + * This must be called for every miplevel. + */ Builder& miplevel(size_t mipIndex, size_t layerIndex, const LinearImage& image) noexcept; - // Creates a BasisU encoder and returns null if an error occurred. + /** + * Creates a BasisU encoder and returns null if an error occurred. + */ BasisEncoder* build(); private: @@ -105,12 +133,28 @@ public: BasisEncoder(BasisEncoder&& that) noexcept; BasisEncoder& operator=(BasisEncoder&& that) noexcept; - // Triggers compression of all miplevels and waits until all jobs are done. - // Returns false if an error occurred. The resulting KTX2 contents can be retrieved - // using the getters below. + /** + * Triggers compression of all miplevels and waits until all jobs are done. + * + * The resulting KTX2 contents can be retrieved using the getters below. + * + * @returns false if an error occurred. + */ bool encode(); + /** + * Gets the number of bytes in the generated KTX2 file. + * + * This can only be called if encode() is successfully called first. + */ size_t getKtx2ByteCount() const noexcept; + + /** + * Gets the content of the generated KTX2 file. + * + * This memory is owned by BasisEncoder and is freed when the encoder is freed. + * This can only be called if encode() is successfully called first. + */ uint8_t const* getKtx2Data() const noexcept; private: diff --git a/libs/imageio/src/BasisEncoder.cpp b/libs/imageio/src/BasisEncoder.cpp index 25f3e5533c..b32bbadb7d 100644 --- a/libs/imageio/src/BasisEncoder.cpp +++ b/libs/imageio/src/BasisEncoder.cpp @@ -46,7 +46,6 @@ struct BasisEncoderImpl { }; Builder::Builder(size_t mipCount, size_t layerCount) noexcept : mImpl(new BasisEncoderBuilderImpl) { - const size_t mipsPerImage = mipCount > 0 ? mipCount : 1; const bool multiple = mImpl->params.m_source_images.size() > 1; mImpl->params.m_tex_type = multiple ? basist::cBASISTexType2DArray : basist::cBASISTexType2D; mImpl->params.m_uastc = true; @@ -200,7 +199,7 @@ BasisEncoder::~BasisEncoder() noexcept { basisu::basisu_encoder_deinit(); } -BasisEncoder::BasisEncoder(BasisEncoder&& that) noexcept { +BasisEncoder::BasisEncoder(BasisEncoder&& that) noexcept : mImpl(nullptr) { std::swap(mImpl, that.mImpl); } diff --git a/libs/ktxreader/include/ktxreader/Ktx2Reader.h b/libs/ktxreader/include/ktxreader/Ktx2Reader.h index 88ff399e8d..a7e76735a3 100644 --- a/libs/ktxreader/include/ktxreader/Ktx2Reader.h +++ b/libs/ktxreader/include/ktxreader/Ktx2Reader.h @@ -38,7 +38,7 @@ class Ktx2Reader { public: using Engine = filament::Engine; using Texture = filament::Texture; - enum TransferFunction { LINEAR, sRGB }; + enum class TransferFunction { LINEAR, sRGB }; Ktx2Reader(Engine& engine, bool quiet = false); ~Ktx2Reader(); @@ -72,7 +72,7 @@ class Ktx2Reader { * Attempts to create a Filament texture from the given KTX2 blob. If none of the requested * formats can be extracted from the data, this returns null. */ - Texture* load(const uint8_t* data, size_t size, TransferFunction transfer = LINEAR); + Texture* load(const uint8_t* data, size_t size, TransferFunction transfer); private: Ktx2Reader(const Ktx2Reader&) = delete; diff --git a/libs/ktxreader/src/Ktx2Reader.cpp b/libs/ktxreader/src/Ktx2Reader.cpp index 1a93a0af7d..5fc30e5011 100644 --- a/libs/ktxreader/src/Ktx2Reader.cpp +++ b/libs/ktxreader/src/Ktx2Reader.cpp @@ -73,8 +73,8 @@ static FinalFormatInfo getFinalFormatInfo(Texture::InternalFormat fmt) { using tt = Texture::Type; using tf = Texture::Format; using ttf = transcoder_texture_format; - const auto sRGB = ktxreader::Ktx2Reader::sRGB; - const auto LINEAR = ktxreader::Ktx2Reader::LINEAR; + const auto sRGB = ktxreader::Ktx2Reader::TransferFunction::sRGB; + const auto LINEAR = ktxreader::Ktx2Reader::TransferFunction::LINEAR; switch (fmt) { case tif::ETC2_EAC_SRGBA8: return {true, true, sRGB, ttf::cTFETC2_RGBA, tct::ETC2_EAC_RGBA8}; case tif::ETC2_EAC_RGBA8: return {true, true, LINEAR, ttf::cTFETC2_RGBA, tct::ETC2_EAC_SRGBA8}; @@ -144,7 +144,8 @@ Texture* Ktx2Reader::load(const uint8_t* data, size_t size, TransferFunction tra return nullptr; } - if (mTranscoder->get_dfd_transfer_func() == KTX2_KHR_DF_TRANSFER_LINEAR && transfer == sRGB) { + if (mTranscoder->get_dfd_transfer_func() == KTX2_KHR_DF_TRANSFER_LINEAR && + transfer == TransferFunction::sRGB) { if (!mQuiet) { utils::slog.e << "Source texture is marked linear, but client is requesting sRGB." << utils::io::endl; @@ -152,7 +153,8 @@ Texture* Ktx2Reader::load(const uint8_t* data, size_t size, TransferFunction tra return nullptr; } - if (mTranscoder->get_dfd_transfer_func() == KTX2_KHR_DF_TRANSFER_SRGB && transfer == LINEAR) { + if (mTranscoder->get_dfd_transfer_func() == KTX2_KHR_DF_TRANSFER_SRGB && + transfer == TransferFunction::LINEAR) { if (!mQuiet) { utils::slog.e << "Source texture is marked sRGB, but client is requesting linear." << utils::io::endl; diff --git a/libs/ktxreader/tests/test_ktxreader.cpp b/libs/ktxreader/tests/test_ktxreader.cpp index c583045232..03aa683cd7 100644 --- a/libs/ktxreader/tests/test_ktxreader.cpp +++ b/libs/ktxreader/tests/test_ktxreader.cpp @@ -81,7 +81,8 @@ TEST_F(KtxReaderTest, Ktx2) { reader.requestFormat(Texture::InternalFormat::SRGB8_A8); reader.requestFormat(Texture::InternalFormat::RGBA8); - Texture* tex = reader.load(contents.data(), contents.size()); + Texture* tex = reader.load(contents.data(), contents.size(), + ktxreader::Ktx2Reader::TransferFunction::LINEAR); ASSERT_TRUE(tex != nullptr); ASSERT_EQ(tex->getFormat(), Texture::InternalFormat::DXT3_RGBA); diff --git a/samples/suzanne.cpp b/samples/suzanne.cpp index 52298a0f2e..dae42d353e 100644 --- a/samples/suzanne.cpp +++ b/samples/suzanne.cpp @@ -154,10 +154,13 @@ int main(int argc, char** argv) { reader.requestFormat(Texture::InternalFormat::SRGB8_A8); reader.requestFormat(Texture::InternalFormat::RGBA8); - app.albedo = reader.load(MONKEY_ALBEDO_DATA, MONKEY_ALBEDO_SIZE, Ktx2Reader::sRGB); - app.ao = reader.load(MONKEY_AO_DATA, MONKEY_AO_SIZE); - app.metallic = reader.load(MONKEY_METALLIC_DATA, MONKEY_METALLIC_SIZE); - app.roughness = reader.load(MONKEY_ROUGHNESS_DATA, MONKEY_ROUGHNESS_SIZE); + constexpr auto sRGB = Ktx2Reader::TransferFunction::sRGB; + constexpr auto LINEAR = Ktx2Reader::TransferFunction::LINEAR; + + app.albedo = reader.load(MONKEY_ALBEDO_DATA, MONKEY_ALBEDO_SIZE, sRGB); + app.ao = reader.load(MONKEY_AO_DATA, MONKEY_AO_SIZE, LINEAR); + app.metallic = reader.load(MONKEY_METALLIC_DATA, MONKEY_METALLIC_SIZE, LINEAR); + app.roughness = reader.load(MONKEY_ROUGHNESS_DATA, MONKEY_ROUGHNESS_SIZE, LINEAR); #if !defined(NDEBUG) using namespace utils; diff --git a/third_party/basisu/tnt/CMakeLists.txt b/third_party/basisu/tnt/CMakeLists.txt index 36a7de2756..5fe88d735a 100644 --- a/third_party/basisu/tnt/CMakeLists.txt +++ b/third_party/basisu/tnt/CMakeLists.txt @@ -62,7 +62,7 @@ target_compile_definitions(basisu PRIVATE ${BASIS_CONFIG}) target_compile_definitions(basis_encoder PRIVATE ${BASIS_CONFIG}) target_compile_definitions(basis_transcoder PRIVATE ${BASIS_CONFIG}) -if (NOT MSVC) +if (NOT MSVC AND NOT ANDROID) target_link_libraries(basisu m pthread) endif()