allow setting bg texture from ktx
This commit is contained in:
@@ -10,7 +10,7 @@ add_library(
|
|||||||
filament_interop
|
filament_interop
|
||||||
SHARED
|
SHARED
|
||||||
src/main/cpp/filament_android.cpp
|
src/main/cpp/filament_android.cpp
|
||||||
src/main/cpp/KtxReader1.cpp
|
../ios/src/ktxreader/Ktx1Reader.cpp
|
||||||
src/main/cpp/StbProvider.cpp
|
src/main/cpp/StbProvider.cpp
|
||||||
src/main/cpp/JobSystem.cpp
|
src/main/cpp/JobSystem.cpp
|
||||||
../ios/src/SceneAssetLoader.cpp
|
../ios/src/SceneAssetLoader.cpp
|
||||||
|
|||||||
3
example/assets/background.ktx
Normal file
3
example/assets/background.ktx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:620bfa644724e9914df19949346ad0f441357fbb176879421d66b4377705836b
|
||||||
|
size 1048644
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c38fa36cbbbe969a2ae015fe56752343e8d06007c6eea28c56e22ffd56a8db1a
|
oid sha256:bedb625eca04a9abba66d1521efda591819212e44cef0ab2438631596902140c
|
||||||
size 2570831
|
size 585493
|
||||||
|
|||||||
@@ -59,8 +59,10 @@
|
|||||||
#include <math/vec4.h>
|
#include <math/vec4.h>
|
||||||
|
|
||||||
#include <ktxreader/Ktx1Reader.h>
|
#include <ktxreader/Ktx1Reader.h>
|
||||||
|
#include <ktxreader/Ktx2Reader.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
@@ -323,46 +325,62 @@ void FilamentViewer::createImageRenderable() {
|
|||||||
|
|
||||||
_imageEntity = &imageEntity;
|
_imageEntity = &imageEntity;
|
||||||
|
|
||||||
Texture *texture = Texture::Builder()
|
|
||||||
.width(1)
|
|
||||||
.height(1)
|
|
||||||
.levels(1)
|
|
||||||
.format(Texture::InternalFormat::RGBA8)
|
|
||||||
.sampler(Texture::Sampler::SAMPLER_2D)
|
|
||||||
.build(*_engine);
|
|
||||||
static uint32_t pixel = 0;
|
|
||||||
Texture::PixelBufferDescriptor buffer(&pixel, 4, Texture::Format::RGBA,
|
|
||||||
Texture::Type::UBYTE);
|
|
||||||
texture->setImage(*_engine, 0, std::move(buffer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FilamentViewer::setBackgroundImage(const char *resourcePath) {
|
static bool endsWith(string path, string ending) {
|
||||||
|
return path.compare(path.length() - ending.length(), ending.length(), ending) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
createImageRenderable();
|
void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) {
|
||||||
|
|
||||||
if (_imageTexture) {
|
// TODO - check all this
|
||||||
_engine->destroy(_imageTexture);
|
|
||||||
_imageTexture = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceBuffer bg = _loadResource(resourcePath);
|
// ktxreader::Ktx2Reader reader(*_engine);
|
||||||
|
|
||||||
polyvox::StreamBufferAdapter sb((char *)bg.data, (char *)bg.data + bg.size);
|
// reader.requestFormat(Texture::InternalFormat::DXT3_SRGBA);
|
||||||
|
// reader.requestFormat(Texture::InternalFormat::DXT3_RGBA);
|
||||||
|
|
||||||
std::istream *inputStream = new std::istream(&sb);
|
// // Uncompressed formats are lower priority, so they get added last.
|
||||||
|
// reader.requestFormat(Texture::InternalFormat::SRGB8_A8);
|
||||||
|
// reader.requestFormat(Texture::InternalFormat::RGBA8);
|
||||||
|
|
||||||
LinearImage *image = new LinearImage(ImageDecoder::decode(
|
// // std::ifstream inputStream("/data/data/app.polyvox.filament_example/foo.ktx", ios::binary);
|
||||||
*inputStream, resourcePath, ImageDecoder::ColorSpace::SRGB));
|
|
||||||
|
// // auto contents = vector<uint8_t>((istreambuf_iterator<char>(inputStream)), {});
|
||||||
|
|
||||||
|
// _imageTexture = reader.load(contents.data(), contents.size(),
|
||||||
|
// ktxreader::Ktx2Reader::TransferFunction::LINEAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) {
|
||||||
|
ktxreader::Ktx1Bundle *bundle =
|
||||||
|
new ktxreader::Ktx1Bundle(static_cast<const uint8_t *>(rb.data),
|
||||||
|
static_cast<uint32_t>(rb.size));
|
||||||
|
_imageTexture =
|
||||||
|
ktxreader::Ktx1Reader::createTexture(_engine, *bundle, false, [](void* userdata) {
|
||||||
|
Ktx1Bundle* bundle = (Ktx1Bundle*) userdata;
|
||||||
|
delete bundle;
|
||||||
|
}, bundle);
|
||||||
|
|
||||||
|
auto info = bundle->getInfo();
|
||||||
|
_imageWidth = info.pixelWidth;
|
||||||
|
_imageHeight = info.pixelHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) {
|
||||||
|
|
||||||
|
polyvox::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size);
|
||||||
|
|
||||||
|
std::istream inputStream(&sb);
|
||||||
|
|
||||||
|
LinearImage* image = new LinearImage(ImageDecoder::decode(
|
||||||
|
inputStream, path.c_str(), ImageDecoder::ColorSpace::SRGB));
|
||||||
|
|
||||||
if (!image->isValid()) {
|
if (!image->isValid()) {
|
||||||
Log("Invalid image : %s", resourcePath);
|
Log("Invalid image : %s", path.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete inputStream;
|
|
||||||
|
|
||||||
_freeResource(bg.id);
|
|
||||||
|
|
||||||
uint32_t channels = image->getChannels();
|
uint32_t channels = image->getChannels();
|
||||||
_imageWidth = image->getWidth();
|
_imageWidth = image->getWidth();
|
||||||
_imageHeight = image->getHeight();
|
_imageHeight = image->getHeight();
|
||||||
@@ -378,18 +396,60 @@ void FilamentViewer::setBackgroundImage(const char *resourcePath) {
|
|||||||
|
|
||||||
Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t,
|
Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t,
|
||||||
void *data) {
|
void *data) {
|
||||||
delete reinterpret_cast<LinearImage *>(data);
|
Log("Deleting LinearImage");
|
||||||
|
delete reinterpret_cast<LinearImage*>(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
Texture::PixelBufferDescriptor buffer(
|
auto pbd = Texture::PixelBufferDescriptor(
|
||||||
image->getPixelRef(), size_t(_imageWidth * _imageHeight * channels * sizeof(float)),
|
image->getPixelRef(), size_t(_imageWidth * _imageHeight * channels * sizeof(float)),
|
||||||
channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA,
|
channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA,
|
||||||
Texture::Type::FLOAT, freeCallback);
|
Texture::Type::FLOAT, nullptr, freeCallback, image);
|
||||||
|
|
||||||
_imageTexture->setImage(*_engine, 0, std::move(buffer));
|
_imageTexture->setImage(*_engine, 0, std::move(pbd));
|
||||||
_imageTexture->generateMipmaps(*_engine);
|
}
|
||||||
|
|
||||||
// This currently just acnhors the image at the bottom left of the viewport at its original size
|
void FilamentViewer::loadTextureFromPath(string path) {
|
||||||
|
string ktxExt(".ktx");
|
||||||
|
string ktx2Ext(".ktx2");
|
||||||
|
string pngExt(".png");
|
||||||
|
|
||||||
|
if (path.length() < 5) {
|
||||||
|
Log("Invalid resource path : %s", path.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceBuffer rb = _loadResource(path.c_str());
|
||||||
|
|
||||||
|
if(endsWith(path, ktxExt)) {
|
||||||
|
loadKtxTexture(path, rb);
|
||||||
|
} else if(endsWith(path, ktx2Ext)) {
|
||||||
|
loadKtx2Texture(path, rb);
|
||||||
|
} else if(endsWith(path, pngExt)) {
|
||||||
|
loadPngTexture(path, rb);
|
||||||
|
}
|
||||||
|
|
||||||
|
_freeResource(rb.id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentViewer::setBackgroundImage(const char *resourcePath) {
|
||||||
|
|
||||||
|
string resourcePathString(resourcePath);
|
||||||
|
|
||||||
|
Log("Setting background image to %s", resourcePath);
|
||||||
|
|
||||||
|
createImageRenderable();
|
||||||
|
|
||||||
|
if (_imageTexture) {
|
||||||
|
Log("Destroying existing texture");
|
||||||
|
_engine->destroy(_imageTexture);
|
||||||
|
Log("Destroyed.");
|
||||||
|
_imageTexture = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTextureFromPath(resourcePathString);
|
||||||
|
|
||||||
|
// This currently just anchors the image at the bottom left of the viewport at its original size
|
||||||
// TODO - implement stretch/etc
|
// TODO - implement stretch/etc
|
||||||
const Viewport& vp = _view->getViewport();
|
const Viewport& vp = _view->getViewport();
|
||||||
Log("Image width %d height %d vp width %d height %d", _imageWidth, _imageHeight, vp.width, vp.height);
|
Log("Image width %d height %d vp width %d height %d", _imageWidth, _imageHeight, vp.width, vp.height);
|
||||||
@@ -671,16 +731,18 @@ bool FilamentViewer::setCamera(SceneAsset *asset, const char *cameraName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FilamentViewer::loadSkybox(const char *const skyboxPath) {
|
void FilamentViewer::loadSkybox(const char *const skyboxPath) {
|
||||||
if (!skyboxPath) {
|
removeSkybox();
|
||||||
_scene->setSkybox(nullptr);
|
if (skyboxPath) {
|
||||||
} else {
|
|
||||||
ResourceBuffer skyboxBuffer = _loadResource(skyboxPath);
|
ResourceBuffer skyboxBuffer = _loadResource(skyboxPath);
|
||||||
|
|
||||||
image::Ktx1Bundle *skyboxBundle =
|
image::Ktx1Bundle *skyboxBundle =
|
||||||
new image::Ktx1Bundle(static_cast<const uint8_t *>(skyboxBuffer.data),
|
new image::Ktx1Bundle(static_cast<const uint8_t *>(skyboxBuffer.data),
|
||||||
static_cast<uint32_t>(skyboxBuffer.size));
|
static_cast<uint32_t>(skyboxBuffer.size));
|
||||||
_skyboxTexture =
|
_skyboxTexture =
|
||||||
ktxreader::Ktx1Reader::createTexture(_engine, skyboxBundle, false);
|
ktxreader::Ktx1Reader::createTexture(_engine, *skyboxBundle, false, [](void* userdata) {
|
||||||
|
image::Ktx1Bundle* bundle = (image::Ktx1Bundle*) userdata;
|
||||||
|
delete bundle;
|
||||||
|
}, skyboxBundle);
|
||||||
_skybox =
|
_skybox =
|
||||||
filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine);
|
filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine);
|
||||||
|
|
||||||
@@ -689,15 +751,29 @@ void FilamentViewer::loadSkybox(const char *const skyboxPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FilamentViewer::removeSkybox() { _scene->setSkybox(nullptr); }
|
void FilamentViewer::removeSkybox() {
|
||||||
|
if(_skybox) {
|
||||||
|
_engine->destroy(_skybox);
|
||||||
|
_engine->destroy(_skyboxTexture);
|
||||||
|
_skybox = nullptr;
|
||||||
|
_skyboxTexture = nullptr;
|
||||||
|
}
|
||||||
|
_scene->setSkybox(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
void FilamentViewer::removeIbl() { _scene->setIndirectLight(nullptr); }
|
void FilamentViewer::removeIbl() {
|
||||||
|
if(_indirectLight) {
|
||||||
|
_engine->destroy(_indirectLight);
|
||||||
|
_engine->destroy(_iblTexture);
|
||||||
|
_indirectLight = nullptr;
|
||||||
|
_iblTexture = nullptr;
|
||||||
|
}
|
||||||
|
_scene->setIndirectLight(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
void FilamentViewer::loadIbl(const char *const iblPath) {
|
void FilamentViewer::loadIbl(const char *const iblPath) {
|
||||||
if (!iblPath) {
|
removeIbl();
|
||||||
_scene->setIndirectLight(nullptr);
|
if (iblPath) {
|
||||||
} else {
|
|
||||||
|
|
||||||
Log("Loading IBL from %s", iblPath);
|
Log("Loading IBL from %s", iblPath);
|
||||||
|
|
||||||
// Load IBL.
|
// Load IBL.
|
||||||
@@ -709,7 +785,10 @@ void FilamentViewer::loadIbl(const char *const iblPath) {
|
|||||||
math::float3 harmonics[9];
|
math::float3 harmonics[9];
|
||||||
iblBundle->getSphericalHarmonics(harmonics);
|
iblBundle->getSphericalHarmonics(harmonics);
|
||||||
_iblTexture =
|
_iblTexture =
|
||||||
ktxreader::Ktx1Reader::createTexture(_engine, iblBundle, false);
|
ktxreader::Ktx1Reader::createTexture(_engine, *iblBundle, false, [](void* userdata) {
|
||||||
|
image::Ktx1Bundle* bundle = (image::Ktx1Bundle*) userdata;
|
||||||
|
delete bundle;
|
||||||
|
}, iblBundle);
|
||||||
_indirectLight = IndirectLight::Builder()
|
_indirectLight = IndirectLight::Builder()
|
||||||
.reflections(_iblTexture)
|
.reflections(_iblTexture)
|
||||||
.irradiance(3, harmonics)
|
.irradiance(3, harmonics)
|
||||||
|
|||||||
@@ -151,6 +151,10 @@ namespace polyvox {
|
|||||||
Material* _imageMaterial = nullptr;
|
Material* _imageMaterial = nullptr;
|
||||||
TextureSampler _imageSampler;
|
TextureSampler _imageSampler;
|
||||||
ColorGrading *colorGrading = nullptr;
|
ColorGrading *colorGrading = nullptr;
|
||||||
|
void loadKtx2Texture(string path, ResourceBuffer data);
|
||||||
|
void loadKtxTexture(string path, ResourceBuffer data);
|
||||||
|
void loadPngTexture(string path, ResourceBuffer data);
|
||||||
|
void loadTextureFromPath(string path);
|
||||||
|
|
||||||
void _createManipulator();
|
void _createManipulator();
|
||||||
uint32_t _lastFrameTimeInNanos;
|
uint32_t _lastFrameTimeInNanos;
|
||||||
|
|||||||
426
ios/src/ktxreader/Ktx2Reader.cpp
Normal file
426
ios/src/ktxreader/Ktx2Reader.cpp
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
/*
|
||||||
|
* 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 <ktxreader/Ktx2Reader.h>
|
||||||
|
|
||||||
|
#include <filament/Engine.h>
|
||||||
|
#include <filament/Texture.h>
|
||||||
|
|
||||||
|
#include <utils/Log.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Warray-bounds"
|
||||||
|
#include <basisu_transcoder.h>
|
||||||
|
#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<uint8_t>;
|
||||||
|
|
||||||
|
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<Texture::PixelBufferDescriptor*>;
|
||||||
|
|
||||||
|
// 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<ktx2_transcoder> 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<FAsync const*>(this)->getTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Async::doTranscoding() {
|
||||||
|
return static_cast<FAsync*>(this)->doTranscoding();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Async::uploadImages() {
|
||||||
|
return static_cast<FAsync*>(this)->uploadImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ktxreader
|
||||||
Reference in New Issue
Block a user