diff --git a/ios/src/ktxreader/Ktx1Reader.cpp b/ios/src/ktxreader/Ktx1Reader.cpp deleted file mode 100644 index 9148d861..00000000 --- a/ios/src/ktxreader/Ktx1Reader.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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 -#include - -#include -#include - -namespace ktxreader { -namespace Ktx1Reader { - -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); - -#ifndef NDEBUG - if (srgb && !isSrgbTextureFormat(texformat)) { - utils::slog.w << "Requested sRGB format but KTX contains a linear format. " - << utils::io::endl; - } else if (!srgb && isSrgbTextureFormat(texformat)) { - utils::slog.w << "Requested linear format but KTX contains a sRGB format. " - << utils::io::endl; - } -#endif - - 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; -} - -bool isSrgbTextureFormat(TextureFormat format) { - switch(format) { - // Non-compressed - case Texture::InternalFormat::RGB8: - case Texture::InternalFormat::RGBA8: - return false; - - // ASTC - case Texture::InternalFormat::RGBA_ASTC_4x4: - case Texture::InternalFormat::RGBA_ASTC_5x4: - case Texture::InternalFormat::RGBA_ASTC_5x5: - case Texture::InternalFormat::RGBA_ASTC_6x5: - case Texture::InternalFormat::RGBA_ASTC_6x6: - case Texture::InternalFormat::RGBA_ASTC_8x5: - case Texture::InternalFormat::RGBA_ASTC_8x6: - case Texture::InternalFormat::RGBA_ASTC_8x8: - case Texture::InternalFormat::RGBA_ASTC_10x5: - case Texture::InternalFormat::RGBA_ASTC_10x6: - case Texture::InternalFormat::RGBA_ASTC_10x8: - case Texture::InternalFormat::RGBA_ASTC_10x10: - case Texture::InternalFormat::RGBA_ASTC_12x10: - case Texture::InternalFormat::RGBA_ASTC_12x12: - return false; - - // ETC2 - case Texture::InternalFormat::ETC2_RGB8: - case Texture::InternalFormat::ETC2_RGB8_A1: - case Texture::InternalFormat::ETC2_EAC_RGBA8: - return false; - - // DXT - case Texture::InternalFormat::DXT1_RGB: - case Texture::InternalFormat::DXT1_RGBA: - case Texture::InternalFormat::DXT3_RGBA: - case Texture::InternalFormat::DXT5_RGBA: - return false; - - default: - return true; - } -} - -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/ios/src/ktxreader/Ktx2Reader.cpp b/ios/src/ktxreader/Ktx2Reader.cpp deleted file mode 100644 index 622e0235..00000000 --- a/ios/src/ktxreader/Ktx2Reader.cpp +++ /dev/null @@ -1,426 +0,0 @@ -/* - * 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 - -#include -#include - -#include - -#include -#include - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warray-bounds" -#include -#pragma clang diagnostic pop - -using namespace basist; -using namespace filament; - -using TransferFunction = ktxreader::Ktx2Reader::TransferFunction; -using Result = ktxreader::Ktx2Reader::Result; -using Async = ktxreader::Ktx2Reader::Async; -using Buffer = std::vector; - -namespace { -struct FinalFormatInfo { - const char* name; // <-- for debug purposes only - bool isSupported; - bool isCompressed; - TransferFunction transferFunction; - transcoder_texture_format basisFormat; - Texture::CompressedType compressedPixelDataType; - Texture::Type pixelDataType; - Texture::Format pixelDataFormat; -}; -} - -// This function returns various information about a Filament internal format, most notably its -// equivalent BasisU enumerant. -// -// Return by value isn't expensive here due to copy elision. -// -// Note that Filament's internal format list mimics the Vulkan format list, which -// embeds transfer function information (i.e. sRGB or not) into the format, whereas -// the basis format list does not. -// -// The following formats supported by BasisU but are not supported by Filament. -// -// transcoder_texture_format::cTFETC1_RGB -// transcoder_texture_format::cTFATC_RGB -// transcoder_texture_format::cTFATC_RGBA -// transcoder_texture_format::cTFFXT1_RGB -// transcoder_texture_format::cTFPVRTC2_4_RGB -// transcoder_texture_format::cTFPVRTC2_4_RGBA -// transcoder_texture_format::cTFPVRTC1_4_RGB -// transcoder_texture_format::cTFPVRTC1_4_RGBA -// transcoder_texture_format::cTFBC4_R -// transcoder_texture_format::cTFBC5_RG -// transcoder_texture_format::cTFBC7_RGBA (this format would add size bloat to the transcoder) -// transcoder_texture_format::cTFBGR565 (note the blue/red swap) -// -static FinalFormatInfo getFinalFormatInfo(Texture::InternalFormat fmt) { - using tif = Texture::InternalFormat; - using tct = Texture::CompressedType; - using tt = Texture::Type; - using tf = Texture::Format; - using ttf = transcoder_texture_format; - const auto sRGB = TransferFunction::sRGB; - const auto LINEAR = TransferFunction::LINEAR; - switch (fmt) { - case tif::ETC2_EAC_SRGBA8: return {"ETC2_EAC_SRGBA8", true, true, sRGB, ttf::cTFETC2_RGBA, tct::ETC2_EAC_RGBA8}; - case tif::ETC2_EAC_RGBA8: return {"ETC2_EAC_RGBA8", true, true, LINEAR, ttf::cTFETC2_RGBA, tct::ETC2_EAC_SRGBA8}; - case tif::DXT1_SRGB: return {"DXT1_SRGB", true, true, sRGB, ttf::cTFBC1_RGB, tct::DXT1_RGB}; - case tif::DXT1_RGB: return {"DXT1_RGB", true, true, LINEAR, ttf::cTFBC1_RGB, tct::DXT1_SRGB}; - case tif::DXT5_SRGBA: return {"DXT5_SRGBA", true, true, sRGB, ttf::cTFBC3_RGBA, tct::DXT5_RGBA}; - case tif::DXT5_RGBA: return {"DXT5_RGBA", true, true, LINEAR, ttf::cTFBC3_RGBA, tct::DXT5_SRGBA}; - case tif::SRGB8_ALPHA8_ASTC_4x4: return {"SRGB8_ALPHA8_ASTC_4x4", true, true, sRGB, ttf::cTFASTC_4x4_RGBA, tct::RGBA_ASTC_4x4}; - case tif::RGBA_ASTC_4x4: return {"RGBA_ASTC_4x4", true, true, LINEAR, ttf::cTFASTC_4x4_RGBA, tct::SRGB8_ALPHA8_ASTC_4x4}; - case tif::EAC_R11: return {"EAC_R11", true, true, LINEAR, ttf::cTFETC2_EAC_R11, tct::EAC_R11}; - - // The following format is useful for normal maps. - // Note that BasisU supports only the unsigned variant. - case tif::EAC_RG11: return {"EAC_RG11", true, true, LINEAR, ttf::cTFETC2_EAC_RG11, tct::EAC_RG11}; - - // Uncompressed formats. - case tif::SRGB8_A8: return {"SRGB8_A8", true, false, sRGB, ttf::cTFRGBA32, {}, tt::UBYTE, tf::RGBA}; - case tif::RGBA8: return {"RGBA8", true, false, LINEAR, ttf::cTFRGBA32, {}, tt::UBYTE, tf::RGBA}; - case tif::RGB565: return {"RGB565", true, false, LINEAR, ttf::cTFRGB565, {}, tt::USHORT_565, tf::RGB}; - case tif::RGBA4: return {"RGBA4", true, false, LINEAR, ttf::cTFRGBA4444, {}, tt::USHORT, tf::RGBA}; - - default: return {}; - } -} - -// In theory we could pass "free" directly into the callback but doing so triggers ASAN warnings. -static void freeCallback(void* buf, size_t, void* userdata) { - free(buf); -} - -// This helper is used by both the asynchronous and synchronous API's. -static Result transcodeImageLevel(ktx2_transcoder& transcoder, - ktx2_transcoder_state& transcoderState, Texture::InternalFormat format, - uint32_t levelIndex, Texture::PixelBufferDescriptor** pbd) { - using basisu::texture_format; - assert_invariant(levelIndex < KTX2_MAX_SUPPORTED_LEVEL_COUNT); - const FinalFormatInfo formatInfo = getFinalFormatInfo(format); - const texture_format destFormat = basis_get_basisu_texture_format(formatInfo.basisFormat); - const uint32_t layerIndex = 0; - const uint32_t faceIndex = 0; - const uint32_t decodeFlags = 0; - const uint32_t outputRowPitch = 0; - const uint32_t outputRowCount = 0; - const int channel0 = 0; - const int channel1 = 0; - - basist::ktx2_image_level_info levelInfo; - transcoder.get_image_level_info(levelInfo, levelIndex, layerIndex, faceIndex); - - if (formatInfo.isCompressed) { - const uint32_t qwordsPerBlock = basisu::get_qwords_per_block(destFormat); - const size_t byteCount = sizeof(uint64_t) * qwordsPerBlock * levelInfo.m_total_blocks; - uint64_t* const blocks = (uint64_t*) malloc(byteCount); - if (!transcoder.transcode_image_level(levelIndex, layerIndex, faceIndex, blocks, - levelInfo.m_total_blocks, formatInfo.basisFormat, decodeFlags, - outputRowPitch, outputRowCount, channel0, - channel1, &transcoderState)) { - return Result::COMPRESSED_TRANSCODE_FAILURE; - } - *pbd = new Texture::PixelBufferDescriptor(blocks, - byteCount, formatInfo.compressedPixelDataType, byteCount, freeCallback); - return Result::SUCCESS; - } - - const uint32_t rowCount = levelInfo.m_orig_height; - const uint32_t bytesPerPix = basis_get_bytes_per_block_or_pixel(formatInfo.basisFormat); - const size_t byteCount = bytesPerPix * levelInfo.m_orig_width * rowCount; - uint64_t* const rows = (uint64_t*) malloc(byteCount); - if (!transcoder.transcode_image_level(levelIndex, layerIndex, faceIndex, rows, - byteCount / bytesPerPix, formatInfo.basisFormat, decodeFlags, - outputRowPitch, outputRowCount, channel0, channel1, &transcoderState)) { - return Result::UNCOMPRESSED_TRANSCODE_FAILURE; - } - *pbd = new Texture::PixelBufferDescriptor(rows, byteCount, - formatInfo.pixelDataFormat, formatInfo.pixelDataType, freeCallback); - return Result::SUCCESS; -} - -namespace ktxreader { - -class FAsync : public Async { -public: - FAsync(Texture* texture, Engine& engine, ktx2_transcoder* transcoder, Buffer&& buf) : - mTexture(texture), mEngine(engine), mTranscoder(transcoder), - mSourceBuffer(std::move(buf)) {} - Texture* getTexture() const noexcept { return mTexture; } - Result doTranscoding(); - void uploadImages(); - -private: - using TranscoderResult = std::atomic; - - // After each level is transcoded, the results are stashed in the following array until the - // foreground thread calls uploadImages(). Each slot in the array corresponds to a single - // miplevel in the texture. - TranscoderResult mTranscoderResults[KTX2_MAX_SUPPORTED_LEVEL_COUNT] = {}; - - Texture* const mTexture; - Engine& mEngine; - - // We do not share the BasisU trancoder between Async objects. The BasisU transcoder - // allows parallelization at "level" granularity, but does not permit parallelization at - // "texture" granularity. i.e. the transcode_image_level() method is thread-safe but the - // start_transcoding() method is not. - std::unique_ptr const mTranscoder; - - // Storage for the content of the KTX2 file. - Buffer mSourceBuffer; -}; - -Ktx2Reader::Ktx2Reader(Engine& engine, bool quiet) : - mEngine(engine), - mQuiet(quiet), - mTranscoder(new ktx2_transcoder()) { - mRequestedFormats.reserve((size_t) transcoder_texture_format::cTFTotalTextureFormats); - basisu_transcoder_init(); -} - -Ktx2Reader::~Ktx2Reader() { - delete mTranscoder; -} - -Result Ktx2Reader::requestFormat(Texture::InternalFormat format) noexcept { - if (!getFinalFormatInfo(format).isSupported) { - return Result::FORMAT_UNSUPPORTED; - } - for (Texture::InternalFormat fmt : mRequestedFormats) { - if (fmt == format) { - return Result::FORMAT_ALREADY_REQUESTED; - } - } - mRequestedFormats.push_back(format); - return Result::SUCCESS; -} - -void Ktx2Reader::unrequestFormat(Texture::InternalFormat format) noexcept { - for (auto iter = mRequestedFormats.begin(); iter != mRequestedFormats.end(); ++iter) { - if (*iter == format) { - mRequestedFormats.erase(iter); - return; - } - } -} - -Texture* Ktx2Reader::load(const void* data, size_t size, TransferFunction transfer) { - Texture* texture = createTexture(mTranscoder, data, size, transfer); - if (texture == nullptr) { - return nullptr; - } - - if (!mTranscoder->start_transcoding()) { - mEngine.destroy(texture); - if (!mQuiet) { - utils::slog.e << "BasisU start_transcoding failed." << utils::io::endl; - } - return nullptr; - } - - ktx2_transcoder_state basisThreadState; - basisThreadState.clear(); - - for (uint32_t levelIndex = 0, n = mTranscoder->get_levels(); levelIndex < n; levelIndex++) { - Texture::PixelBufferDescriptor* pbd; - Result result = transcodeImageLevel(*mTranscoder, basisThreadState, texture->getFormat(), - levelIndex, &pbd); - if (UTILS_UNLIKELY(result != Result::SUCCESS)) { - mEngine.destroy(texture); - if (!mQuiet) { - utils::slog.e << "Failed to transcode level " << levelIndex << utils::io::endl; - } - return nullptr; - } - texture->setImage(mEngine, levelIndex, std::move(*pbd)); - } - return texture; -} - -Result FAsync::doTranscoding() { - ktx2_transcoder_state basisThreadState; - basisThreadState.clear(); - for (uint32_t levelIndex = 0, n = mTranscoder->get_levels(); levelIndex < n; levelIndex++) { - Texture::PixelBufferDescriptor* pbd; - Result result = transcodeImageLevel(*mTranscoder, basisThreadState, mTexture->getFormat(), - levelIndex, &pbd); - if (UTILS_UNLIKELY(result != Result::SUCCESS)) { - return result; - } - mTranscoderResults[levelIndex].store(pbd); - } - return Result::SUCCESS; -} - -void FAsync::uploadImages() { - size_t levelIndex = 0; - UTILS_NOUNROLL - for (TranscoderResult& level : mTranscoderResults) { - Texture::PixelBufferDescriptor* pbd = level.load(); - if (pbd) { - level.store(nullptr); - mTexture->setImage(mEngine, levelIndex, std::move(*pbd)); - delete pbd; - } - ++levelIndex; - } -} - -Async* Ktx2Reader::asyncCreate(const void* data, size_t size, TransferFunction transfer) { - Buffer ktx2content((uint8_t*)data, (uint8_t*)data + size); - ktx2_transcoder* transcoder = new ktx2_transcoder(); - Texture* texture = createTexture(transcoder, ktx2content.data(), ktx2content.size(), transfer); - if (texture == nullptr) { - delete transcoder; - return nullptr; - } - if (!transcoder->start_transcoding()) { - delete transcoder; - mEngine.destroy(texture); - return nullptr; - } - // There's no need to do any further work at this point but it should be noted that this is the - // point at which we first come to know the number of miplevels, dimensions, etc. If we had a - // dynamically sized array to store decoder results, we would reserve it here. - return new FAsync(texture, mEngine, transcoder, std::move(ktx2content)); -} - -void Ktx2Reader::asyncDestroy(Async** async) { - delete *async; - *async = nullptr; -} - -Texture* Ktx2Reader::createTexture(ktx2_transcoder* transcoder, const void* data, size_t size, - TransferFunction transfer) { - if (!transcoder->init(data, size)) { - if (!mQuiet) { - utils::slog.e << "BasisU transcoder init failed." << utils::io::endl; - } - return nullptr; - } - - if (transcoder->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; - } - return nullptr; - } - - if (transcoder->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; - } - return nullptr; - } - - // TODO: support cubemaps. For now we use KTX1 for cubemaps because basisu does not support HDR. - if (transcoder->get_faces() == 6) { - if (!mQuiet) { - utils::slog.e << "Cubemaps are not yet supported." << utils::io::endl; - } - return nullptr; - } - - // TODO: support texture arrays. - if (transcoder->get_layers() > 1) { - if (!mQuiet) { - utils::slog.e << "Texture arrays are not yet supported." << utils::io::endl; - } - return nullptr; - } - - // First pass through, just to make sure we can transcode it. - bool found = false; - Texture::InternalFormat resolvedFormat; - FinalFormatInfo info; - for (Texture::InternalFormat requestedFormat : mRequestedFormats) { - if (!Texture::isTextureFormatSupported(mEngine, requestedFormat)) { - continue; - } - info = getFinalFormatInfo(requestedFormat); - if (!info.isSupported || info.transferFunction != transfer) { - continue; - } - if (!basis_is_format_supported(info.basisFormat, transcoder->get_format())) { - continue; - } - const uint32_t layerIndex = 0; - const uint32_t faceIndex = 0; - for (uint32_t levelIndex = 0; levelIndex < transcoder->get_levels(); levelIndex++) { - basist::ktx2_image_level_info info; - if (!transcoder->get_image_level_info(info, levelIndex, layerIndex, faceIndex)) { - continue; - } - } - found = true; - resolvedFormat = requestedFormat; - break; - } - - if (!found) { - if (!mQuiet) { - utils::slog.e << "Unable to decode any of the requested formats." << utils::io::endl; - } - return nullptr; - } - - Texture* texture = Texture::Builder() - .width(transcoder->get_width()) - .height(transcoder->get_height()) - .levels(transcoder->get_levels()) - .sampler(Texture::Sampler::SAMPLER_2D) - .format(resolvedFormat) - .build(mEngine); - - if (texture == nullptr && !mQuiet) { - utils::slog.e << "Unable to construct texture using BasisU info." << utils::io::endl; - } - - #if BASISU_FORCE_DEVEL_MESSAGES - utils::slog.e << "Ktx2Reader created " - << transcoder->get_width() << "x" << transcoder->get_height() << " texture with format " - << info.name << utils::io::endl; - #endif - - return texture; -} - -Texture* Async::getTexture() const noexcept { - return static_cast(this)->getTexture(); -} - -Result Async::doTranscoding() { - return static_cast(this)->doTranscoding(); -} - -void Async::uploadImages() { - return static_cast(this)->uploadImages(); -} - -} // namespace ktxreader diff --git a/macos/src/ktxreader/Ktx1Reader.cpp b/macos/src/ktxreader/Ktx1Reader.cpp deleted file mode 100644 index 9148d861..00000000 --- a/macos/src/ktxreader/Ktx1Reader.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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 -#include - -#include -#include - -namespace ktxreader { -namespace Ktx1Reader { - -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); - -#ifndef NDEBUG - if (srgb && !isSrgbTextureFormat(texformat)) { - utils::slog.w << "Requested sRGB format but KTX contains a linear format. " - << utils::io::endl; - } else if (!srgb && isSrgbTextureFormat(texformat)) { - utils::slog.w << "Requested linear format but KTX contains a sRGB format. " - << utils::io::endl; - } -#endif - - 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; -} - -bool isSrgbTextureFormat(TextureFormat format) { - switch(format) { - // Non-compressed - case Texture::InternalFormat::RGB8: - case Texture::InternalFormat::RGBA8: - return false; - - // ASTC - case Texture::InternalFormat::RGBA_ASTC_4x4: - case Texture::InternalFormat::RGBA_ASTC_5x4: - case Texture::InternalFormat::RGBA_ASTC_5x5: - case Texture::InternalFormat::RGBA_ASTC_6x5: - case Texture::InternalFormat::RGBA_ASTC_6x6: - case Texture::InternalFormat::RGBA_ASTC_8x5: - case Texture::InternalFormat::RGBA_ASTC_8x6: - case Texture::InternalFormat::RGBA_ASTC_8x8: - case Texture::InternalFormat::RGBA_ASTC_10x5: - case Texture::InternalFormat::RGBA_ASTC_10x6: - case Texture::InternalFormat::RGBA_ASTC_10x8: - case Texture::InternalFormat::RGBA_ASTC_10x10: - case Texture::InternalFormat::RGBA_ASTC_12x10: - case Texture::InternalFormat::RGBA_ASTC_12x12: - return false; - - // ETC2 - case Texture::InternalFormat::ETC2_RGB8: - case Texture::InternalFormat::ETC2_RGB8_A1: - case Texture::InternalFormat::ETC2_EAC_RGBA8: - return false; - - // DXT - case Texture::InternalFormat::DXT1_RGB: - case Texture::InternalFormat::DXT1_RGBA: - case Texture::InternalFormat::DXT3_RGBA: - case Texture::InternalFormat::DXT5_RGBA: - return false; - - default: - return true; - } -} - -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/macos/src/ktxreader/Ktx2Reader.cpp b/macos/src/ktxreader/Ktx2Reader.cpp deleted file mode 100644 index 622e0235..00000000 --- a/macos/src/ktxreader/Ktx2Reader.cpp +++ /dev/null @@ -1,426 +0,0 @@ -/* - * 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 - -#include -#include - -#include - -#include -#include - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warray-bounds" -#include -#pragma clang diagnostic pop - -using namespace basist; -using namespace filament; - -using TransferFunction = ktxreader::Ktx2Reader::TransferFunction; -using Result = ktxreader::Ktx2Reader::Result; -using Async = ktxreader::Ktx2Reader::Async; -using Buffer = std::vector; - -namespace { -struct FinalFormatInfo { - const char* name; // <-- for debug purposes only - bool isSupported; - bool isCompressed; - TransferFunction transferFunction; - transcoder_texture_format basisFormat; - Texture::CompressedType compressedPixelDataType; - Texture::Type pixelDataType; - Texture::Format pixelDataFormat; -}; -} - -// This function returns various information about a Filament internal format, most notably its -// equivalent BasisU enumerant. -// -// Return by value isn't expensive here due to copy elision. -// -// Note that Filament's internal format list mimics the Vulkan format list, which -// embeds transfer function information (i.e. sRGB or not) into the format, whereas -// the basis format list does not. -// -// The following formats supported by BasisU but are not supported by Filament. -// -// transcoder_texture_format::cTFETC1_RGB -// transcoder_texture_format::cTFATC_RGB -// transcoder_texture_format::cTFATC_RGBA -// transcoder_texture_format::cTFFXT1_RGB -// transcoder_texture_format::cTFPVRTC2_4_RGB -// transcoder_texture_format::cTFPVRTC2_4_RGBA -// transcoder_texture_format::cTFPVRTC1_4_RGB -// transcoder_texture_format::cTFPVRTC1_4_RGBA -// transcoder_texture_format::cTFBC4_R -// transcoder_texture_format::cTFBC5_RG -// transcoder_texture_format::cTFBC7_RGBA (this format would add size bloat to the transcoder) -// transcoder_texture_format::cTFBGR565 (note the blue/red swap) -// -static FinalFormatInfo getFinalFormatInfo(Texture::InternalFormat fmt) { - using tif = Texture::InternalFormat; - using tct = Texture::CompressedType; - using tt = Texture::Type; - using tf = Texture::Format; - using ttf = transcoder_texture_format; - const auto sRGB = TransferFunction::sRGB; - const auto LINEAR = TransferFunction::LINEAR; - switch (fmt) { - case tif::ETC2_EAC_SRGBA8: return {"ETC2_EAC_SRGBA8", true, true, sRGB, ttf::cTFETC2_RGBA, tct::ETC2_EAC_RGBA8}; - case tif::ETC2_EAC_RGBA8: return {"ETC2_EAC_RGBA8", true, true, LINEAR, ttf::cTFETC2_RGBA, tct::ETC2_EAC_SRGBA8}; - case tif::DXT1_SRGB: return {"DXT1_SRGB", true, true, sRGB, ttf::cTFBC1_RGB, tct::DXT1_RGB}; - case tif::DXT1_RGB: return {"DXT1_RGB", true, true, LINEAR, ttf::cTFBC1_RGB, tct::DXT1_SRGB}; - case tif::DXT5_SRGBA: return {"DXT5_SRGBA", true, true, sRGB, ttf::cTFBC3_RGBA, tct::DXT5_RGBA}; - case tif::DXT5_RGBA: return {"DXT5_RGBA", true, true, LINEAR, ttf::cTFBC3_RGBA, tct::DXT5_SRGBA}; - case tif::SRGB8_ALPHA8_ASTC_4x4: return {"SRGB8_ALPHA8_ASTC_4x4", true, true, sRGB, ttf::cTFASTC_4x4_RGBA, tct::RGBA_ASTC_4x4}; - case tif::RGBA_ASTC_4x4: return {"RGBA_ASTC_4x4", true, true, LINEAR, ttf::cTFASTC_4x4_RGBA, tct::SRGB8_ALPHA8_ASTC_4x4}; - case tif::EAC_R11: return {"EAC_R11", true, true, LINEAR, ttf::cTFETC2_EAC_R11, tct::EAC_R11}; - - // The following format is useful for normal maps. - // Note that BasisU supports only the unsigned variant. - case tif::EAC_RG11: return {"EAC_RG11", true, true, LINEAR, ttf::cTFETC2_EAC_RG11, tct::EAC_RG11}; - - // Uncompressed formats. - case tif::SRGB8_A8: return {"SRGB8_A8", true, false, sRGB, ttf::cTFRGBA32, {}, tt::UBYTE, tf::RGBA}; - case tif::RGBA8: return {"RGBA8", true, false, LINEAR, ttf::cTFRGBA32, {}, tt::UBYTE, tf::RGBA}; - case tif::RGB565: return {"RGB565", true, false, LINEAR, ttf::cTFRGB565, {}, tt::USHORT_565, tf::RGB}; - case tif::RGBA4: return {"RGBA4", true, false, LINEAR, ttf::cTFRGBA4444, {}, tt::USHORT, tf::RGBA}; - - default: return {}; - } -} - -// In theory we could pass "free" directly into the callback but doing so triggers ASAN warnings. -static void freeCallback(void* buf, size_t, void* userdata) { - free(buf); -} - -// This helper is used by both the asynchronous and synchronous API's. -static Result transcodeImageLevel(ktx2_transcoder& transcoder, - ktx2_transcoder_state& transcoderState, Texture::InternalFormat format, - uint32_t levelIndex, Texture::PixelBufferDescriptor** pbd) { - using basisu::texture_format; - assert_invariant(levelIndex < KTX2_MAX_SUPPORTED_LEVEL_COUNT); - const FinalFormatInfo formatInfo = getFinalFormatInfo(format); - const texture_format destFormat = basis_get_basisu_texture_format(formatInfo.basisFormat); - const uint32_t layerIndex = 0; - const uint32_t faceIndex = 0; - const uint32_t decodeFlags = 0; - const uint32_t outputRowPitch = 0; - const uint32_t outputRowCount = 0; - const int channel0 = 0; - const int channel1 = 0; - - basist::ktx2_image_level_info levelInfo; - transcoder.get_image_level_info(levelInfo, levelIndex, layerIndex, faceIndex); - - if (formatInfo.isCompressed) { - const uint32_t qwordsPerBlock = basisu::get_qwords_per_block(destFormat); - const size_t byteCount = sizeof(uint64_t) * qwordsPerBlock * levelInfo.m_total_blocks; - uint64_t* const blocks = (uint64_t*) malloc(byteCount); - if (!transcoder.transcode_image_level(levelIndex, layerIndex, faceIndex, blocks, - levelInfo.m_total_blocks, formatInfo.basisFormat, decodeFlags, - outputRowPitch, outputRowCount, channel0, - channel1, &transcoderState)) { - return Result::COMPRESSED_TRANSCODE_FAILURE; - } - *pbd = new Texture::PixelBufferDescriptor(blocks, - byteCount, formatInfo.compressedPixelDataType, byteCount, freeCallback); - return Result::SUCCESS; - } - - const uint32_t rowCount = levelInfo.m_orig_height; - const uint32_t bytesPerPix = basis_get_bytes_per_block_or_pixel(formatInfo.basisFormat); - const size_t byteCount = bytesPerPix * levelInfo.m_orig_width * rowCount; - uint64_t* const rows = (uint64_t*) malloc(byteCount); - if (!transcoder.transcode_image_level(levelIndex, layerIndex, faceIndex, rows, - byteCount / bytesPerPix, formatInfo.basisFormat, decodeFlags, - outputRowPitch, outputRowCount, channel0, channel1, &transcoderState)) { - return Result::UNCOMPRESSED_TRANSCODE_FAILURE; - } - *pbd = new Texture::PixelBufferDescriptor(rows, byteCount, - formatInfo.pixelDataFormat, formatInfo.pixelDataType, freeCallback); - return Result::SUCCESS; -} - -namespace ktxreader { - -class FAsync : public Async { -public: - FAsync(Texture* texture, Engine& engine, ktx2_transcoder* transcoder, Buffer&& buf) : - mTexture(texture), mEngine(engine), mTranscoder(transcoder), - mSourceBuffer(std::move(buf)) {} - Texture* getTexture() const noexcept { return mTexture; } - Result doTranscoding(); - void uploadImages(); - -private: - using TranscoderResult = std::atomic; - - // After each level is transcoded, the results are stashed in the following array until the - // foreground thread calls uploadImages(). Each slot in the array corresponds to a single - // miplevel in the texture. - TranscoderResult mTranscoderResults[KTX2_MAX_SUPPORTED_LEVEL_COUNT] = {}; - - Texture* const mTexture; - Engine& mEngine; - - // We do not share the BasisU trancoder between Async objects. The BasisU transcoder - // allows parallelization at "level" granularity, but does not permit parallelization at - // "texture" granularity. i.e. the transcode_image_level() method is thread-safe but the - // start_transcoding() method is not. - std::unique_ptr const mTranscoder; - - // Storage for the content of the KTX2 file. - Buffer mSourceBuffer; -}; - -Ktx2Reader::Ktx2Reader(Engine& engine, bool quiet) : - mEngine(engine), - mQuiet(quiet), - mTranscoder(new ktx2_transcoder()) { - mRequestedFormats.reserve((size_t) transcoder_texture_format::cTFTotalTextureFormats); - basisu_transcoder_init(); -} - -Ktx2Reader::~Ktx2Reader() { - delete mTranscoder; -} - -Result Ktx2Reader::requestFormat(Texture::InternalFormat format) noexcept { - if (!getFinalFormatInfo(format).isSupported) { - return Result::FORMAT_UNSUPPORTED; - } - for (Texture::InternalFormat fmt : mRequestedFormats) { - if (fmt == format) { - return Result::FORMAT_ALREADY_REQUESTED; - } - } - mRequestedFormats.push_back(format); - return Result::SUCCESS; -} - -void Ktx2Reader::unrequestFormat(Texture::InternalFormat format) noexcept { - for (auto iter = mRequestedFormats.begin(); iter != mRequestedFormats.end(); ++iter) { - if (*iter == format) { - mRequestedFormats.erase(iter); - return; - } - } -} - -Texture* Ktx2Reader::load(const void* data, size_t size, TransferFunction transfer) { - Texture* texture = createTexture(mTranscoder, data, size, transfer); - if (texture == nullptr) { - return nullptr; - } - - if (!mTranscoder->start_transcoding()) { - mEngine.destroy(texture); - if (!mQuiet) { - utils::slog.e << "BasisU start_transcoding failed." << utils::io::endl; - } - return nullptr; - } - - ktx2_transcoder_state basisThreadState; - basisThreadState.clear(); - - for (uint32_t levelIndex = 0, n = mTranscoder->get_levels(); levelIndex < n; levelIndex++) { - Texture::PixelBufferDescriptor* pbd; - Result result = transcodeImageLevel(*mTranscoder, basisThreadState, texture->getFormat(), - levelIndex, &pbd); - if (UTILS_UNLIKELY(result != Result::SUCCESS)) { - mEngine.destroy(texture); - if (!mQuiet) { - utils::slog.e << "Failed to transcode level " << levelIndex << utils::io::endl; - } - return nullptr; - } - texture->setImage(mEngine, levelIndex, std::move(*pbd)); - } - return texture; -} - -Result FAsync::doTranscoding() { - ktx2_transcoder_state basisThreadState; - basisThreadState.clear(); - for (uint32_t levelIndex = 0, n = mTranscoder->get_levels(); levelIndex < n; levelIndex++) { - Texture::PixelBufferDescriptor* pbd; - Result result = transcodeImageLevel(*mTranscoder, basisThreadState, mTexture->getFormat(), - levelIndex, &pbd); - if (UTILS_UNLIKELY(result != Result::SUCCESS)) { - return result; - } - mTranscoderResults[levelIndex].store(pbd); - } - return Result::SUCCESS; -} - -void FAsync::uploadImages() { - size_t levelIndex = 0; - UTILS_NOUNROLL - for (TranscoderResult& level : mTranscoderResults) { - Texture::PixelBufferDescriptor* pbd = level.load(); - if (pbd) { - level.store(nullptr); - mTexture->setImage(mEngine, levelIndex, std::move(*pbd)); - delete pbd; - } - ++levelIndex; - } -} - -Async* Ktx2Reader::asyncCreate(const void* data, size_t size, TransferFunction transfer) { - Buffer ktx2content((uint8_t*)data, (uint8_t*)data + size); - ktx2_transcoder* transcoder = new ktx2_transcoder(); - Texture* texture = createTexture(transcoder, ktx2content.data(), ktx2content.size(), transfer); - if (texture == nullptr) { - delete transcoder; - return nullptr; - } - if (!transcoder->start_transcoding()) { - delete transcoder; - mEngine.destroy(texture); - return nullptr; - } - // There's no need to do any further work at this point but it should be noted that this is the - // point at which we first come to know the number of miplevels, dimensions, etc. If we had a - // dynamically sized array to store decoder results, we would reserve it here. - return new FAsync(texture, mEngine, transcoder, std::move(ktx2content)); -} - -void Ktx2Reader::asyncDestroy(Async** async) { - delete *async; - *async = nullptr; -} - -Texture* Ktx2Reader::createTexture(ktx2_transcoder* transcoder, const void* data, size_t size, - TransferFunction transfer) { - if (!transcoder->init(data, size)) { - if (!mQuiet) { - utils::slog.e << "BasisU transcoder init failed." << utils::io::endl; - } - return nullptr; - } - - if (transcoder->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; - } - return nullptr; - } - - if (transcoder->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; - } - return nullptr; - } - - // TODO: support cubemaps. For now we use KTX1 for cubemaps because basisu does not support HDR. - if (transcoder->get_faces() == 6) { - if (!mQuiet) { - utils::slog.e << "Cubemaps are not yet supported." << utils::io::endl; - } - return nullptr; - } - - // TODO: support texture arrays. - if (transcoder->get_layers() > 1) { - if (!mQuiet) { - utils::slog.e << "Texture arrays are not yet supported." << utils::io::endl; - } - return nullptr; - } - - // First pass through, just to make sure we can transcode it. - bool found = false; - Texture::InternalFormat resolvedFormat; - FinalFormatInfo info; - for (Texture::InternalFormat requestedFormat : mRequestedFormats) { - if (!Texture::isTextureFormatSupported(mEngine, requestedFormat)) { - continue; - } - info = getFinalFormatInfo(requestedFormat); - if (!info.isSupported || info.transferFunction != transfer) { - continue; - } - if (!basis_is_format_supported(info.basisFormat, transcoder->get_format())) { - continue; - } - const uint32_t layerIndex = 0; - const uint32_t faceIndex = 0; - for (uint32_t levelIndex = 0; levelIndex < transcoder->get_levels(); levelIndex++) { - basist::ktx2_image_level_info info; - if (!transcoder->get_image_level_info(info, levelIndex, layerIndex, faceIndex)) { - continue; - } - } - found = true; - resolvedFormat = requestedFormat; - break; - } - - if (!found) { - if (!mQuiet) { - utils::slog.e << "Unable to decode any of the requested formats." << utils::io::endl; - } - return nullptr; - } - - Texture* texture = Texture::Builder() - .width(transcoder->get_width()) - .height(transcoder->get_height()) - .levels(transcoder->get_levels()) - .sampler(Texture::Sampler::SAMPLER_2D) - .format(resolvedFormat) - .build(mEngine); - - if (texture == nullptr && !mQuiet) { - utils::slog.e << "Unable to construct texture using BasisU info." << utils::io::endl; - } - - #if BASISU_FORCE_DEVEL_MESSAGES - utils::slog.e << "Ktx2Reader created " - << transcoder->get_width() << "x" << transcoder->get_height() << " texture with format " - << info.name << utils::io::endl; - #endif - - return texture; -} - -Texture* Async::getTexture() const noexcept { - return static_cast(this)->getTexture(); -} - -Result Async::doTranscoding() { - return static_cast(this)->doTranscoding(); -} - -void Async::uploadImages() { - return static_cast(this)->uploadImages(); -} - -} // namespace ktxreader