GLTFIO Webp Texture Support (#9406)

This commit is contained in:
Benn Herrera
2025-12-23 11:40:01 -08:00
committed by GitHub
parent 53f506670c
commit 7f61eb7a0a
19 changed files with 478 additions and 8 deletions

View File

@@ -563,6 +563,10 @@ if (FILAMENT_SUPPORTS_WEBGPU)
add_definitions(-DFILAMENT_SUPPORTS_WEBGPU)
endif()
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
endif()
# Build with Metal support on non-WebGL Apple platforms.
if (APPLE AND NOT WEBGL)
option(FILAMENT_SUPPORTS_METAL "Include the Metal backend" ON)
@@ -915,7 +919,6 @@ endif()
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
add_subdirectory(${EXTERNAL}/libwebp/tnt)
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
endif()
if (FILAMENT_SUPPORTS_VULKAN)

View File

@@ -6,3 +6,4 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- gltfio: Add optional support for webp textures (EXT_texture_webp), controlled via FILAMENT_SUPPORTS_WEBP_TEXTURES cmake option

View File

@@ -6,6 +6,7 @@ option(FILAMENT_ENABLE_FGVIEWER "Enables Frame Graph Viewer" OFF)
option(FILAMENT_ENABLE_MATDBG "Enables Material debugger" OFF)
option(FILAMENT_DISABLE_MATOPT "Disables material optimizations" OFF)
option(FILAMENT_SUPPORTS_WEBGPU "Enables WebGPU on Android" OFF)
option(FILAMENT_SUPPORTS_WEBP_TEXTURES "Enable webp texture support on Android" OFF)
set(CMAKE_CXX_STANDARD 20)
@@ -30,6 +31,12 @@ add_library(stb STATIC IMPORTED)
set_target_properties(stb PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libstb.a)
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
add_library(webpdecoder STATIC IMPORTED)
set_target_properties(webpdecoder PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libwebpdecoder.a)
endif()
add_library(basis_transcoder STATIC IMPORTED)
set_target_properties(basis_transcoder PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbasis_transcoder.a)
@@ -81,6 +88,10 @@ set(GLTFIO_INCLUDE_DIRS
../../libs/ktxreader/include
)
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
list(APPEND GLTFIO_INCLUDE_DIRS ../../third_party/libwebp/src)
endif()
add_library(gltfio-jni SHARED ${GLTFIO_SRCS})
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
@@ -107,3 +118,6 @@ target_link_libraries(gltfio-jni
PRIVATE perfetto # needed only when FILAMENT_ENABLE_PERFETTO is defined
PRIVATE log # needed only when perfetto above is used
)
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
target_link_libraries(gltfio-jni PRIVATE webpdecoder)
endif()

View File

@@ -128,6 +128,18 @@ Java_com_google_android_filament_gltfio_ResourceLoader_nCreateKtx2Provider(JNIEn
return (jlong) createKtx2Provider(engine);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_gltfio_ResourceLoader_nIsWebpSupported(JNIEnv*, jclass) {
return (jboolean) isWebpSupported();
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_gltfio_ResourceLoader_nCreateWebpProvider(JNIEnv*, jclass,
jlong nativeEngine) {
Engine* engine = (Engine*) nativeEngine;
return (jlong) createWebpProvider(engine);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_ResourceLoader_nDestroyTextureProvider(JNIEnv*, jclass,
jlong nativeProvider) {

View File

@@ -37,6 +37,7 @@ public class ResourceLoader {
private final long mNativeObject;
private final long mNativeStbProvider;
private final long mNativeKtx2Provider;
private final long mNativeWebpProvider;
/**
* Constructs a resource loader tied to the given Filament engine.
@@ -50,9 +51,17 @@ public class ResourceLoader {
mNativeObject = nCreateResourceLoader(nativeEngine, false);
mNativeStbProvider = nCreateStbProvider(nativeEngine);
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
nAddTextureProvider(mNativeObject, "image/png", mNativeStbProvider);
nAddTextureProvider(mNativeObject, "image/ktx2", mNativeKtx2Provider);
if (nIsWebpSupported()) {
mNativeWebpProvider = nCreateWebpProvider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/webp", mNativeWebpProvider);
}
else {
mNativeWebpProvider = 0;
}
}
/**
@@ -68,9 +77,17 @@ public class ResourceLoader {
mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights);
mNativeStbProvider = nCreateStbProvider(nativeEngine);
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
nAddTextureProvider(mNativeObject, "image/png", mNativeStbProvider);
nAddTextureProvider(mNativeObject, "image/ktx2", mNativeKtx2Provider);
if (nIsWebpSupported()) {
mNativeWebpProvider = nCreateWebpProvider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/webp", mNativeWebpProvider);
}
else {
mNativeWebpProvider = 0;
}
}
/**
@@ -80,6 +97,9 @@ public class ResourceLoader {
nDestroyResourceLoader(mNativeObject);
nDestroyTextureProvider(mNativeStbProvider);
nDestroyTextureProvider(mNativeKtx2Provider);
if (nIsWebpSupported()) {
nDestroyTextureProvider(mNativeWebpProvider);
}
}
/**
@@ -191,6 +211,9 @@ public class ResourceLoader {
private static native long nCreateStbProvider(long nativeEngine);
private static native long nCreateKtx2Provider(long nativeEngine);
private static native boolean nIsWebpSupported();
private static native long nCreateWebpProvider(long nativeEngine);
private static native void nAddTextureProvider(long nativeLoader, String url, long nativeProvider);
private static native void nDestroyTextureProvider(long nativeProvider);
}

View File

@@ -307,7 +307,7 @@ class FilamentViewer extends LitElement {
asyncInterval: 30
};
const doneAddingResources = (resourceLoader, stbProvider, ktx2Provider) => {
const doneAddingResources = (resourceLoader, stbProvider, ktx2Provider, webpProvider) => {
this.srcBlobResources = {};
resourceLoader.asyncBeginLoad(this.asset);
const timer = setInterval(() => {
@@ -318,6 +318,9 @@ class FilamentViewer extends LitElement {
resourceLoader.delete();
stbProvider.delete();
ktx2Provider.delete();
if (webpProvider) {
webpProvider.delete();
}
this.animator = this.asset.getInstance().getAnimator();
this.animationStartTime = Date.now();
}
@@ -335,18 +338,25 @@ class FilamentViewer extends LitElement {
const stbProvider = new Filament.gltfio$StbProvider(this.engine);
const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine);
let webpProvider = null;
resourceLoader.addStbProvider("image/jpeg", stbProvider);
resourceLoader.addStbProvider("image/png", stbProvider);
resourceLoader.addKtx2Provider("image/ktx2", ktx2Provider);
if (Filament.gltfio$WebpProvider.isWebpSupported()) {
webpProvider = new Filament.gltfio$WebpProvider(this.engine);
resourceLoader.addWebpProvider("image/webp", webpProvider);
}
let remaining = Object.keys(this.srcBlobResources).length;
for (const name in this.srcBlobResources) {
this.srcBlobResources[name].arrayBuffer().then(buffer => {
const desc = getBufferDescriptor(new Uint8Array(buffer));
resourceLoader.addResourceData(name, getBufferDescriptor(desc));
if (--remaining === 0) {
doneAddingResources(resourceLoader, stbProvider, ktx2Provider);
doneAddingResources(resourceLoader, stbProvider, ktx2Provider, webpProvider);
}
});
}

View File

@@ -82,6 +82,7 @@ const float kSensitivity = 100.0f;
Manipulator<float>* _manipulator;
TextureProvider* _stbDecoder;
TextureProvider* _ktxDecoder;
TextureProvider* _webpDecoder;
FilamentAsset* _asset;
@@ -135,9 +136,19 @@ const float kSensitivity = 100.0f;
_resourceLoader = new ResourceLoader({.engine = _engine, .normalizeSkinningWeights = true});
_stbDecoder = createStbProvider(_engine);
_ktxDecoder = createKtx2Provider(_engine);
if (isWebpSupported()) {
_webpDecoder = createWebpProvider(_engine);
}
else {
_webpDecoder = nullptr;
}
_resourceLoader->addTextureProvider("image/png", _stbDecoder);
_resourceLoader->addTextureProvider("image/jpeg", _stbDecoder);
_resourceLoader->addTextureProvider("image/ktx2", _ktxDecoder);
if (_webpDecoder) {
_resourceLoader->addTextureProvider("image/webp", _webpDecoder);
}
_manipulator =
Manipulator<float>::Builder().orbitHomePosition(0.0f, 0.0f, 4.0f).build(Mode::ORBIT);
@@ -285,6 +296,7 @@ const float kSensitivity = 100.0f;
delete _manipulator;
delete _stbDecoder;
delete _ktxDecoder;
delete _webpDecoder;
_engine->destroy(_swapChain);
_engine->destroy(_view);

View File

@@ -146,15 +146,22 @@ void App::setupMesh() {
});
auto stbDecoder = filament::gltfio::createStbProvider(engine);
auto ktxDecoder = filament::gltfio::createKtx2Provider(engine);
filament::gltfio::TextureProvider* webpDecoder = nullptr;
resourceLoader->addTextureProvider("image/png", stbDecoder);
resourceLoader->addTextureProvider("image/jpeg", stbDecoder);
resourceLoader->addTextureProvider("image/ktx2", ktxDecoder);
if (filament::gltfio::isWebpSupported()) {
webpDecoder = filament::gltfio::createWebpProvider(engine);
resourceLoader->addTextureProvider("image/webp", webpDecoder);
}
resourceLoader->loadResources(app.asset);
delete resourceLoader;
delete stbDecoder;
delete ktxDecoder;
delete webpDecoder;
scene->addEntities(app.asset->getEntities(), app.asset->getEntityCount());
}

View File

@@ -49,6 +49,7 @@ set(SRCS
src/UbershaderProvider.cpp
src/Utility.cpp
src/Utility.h
src/WebpProvider.cpp
src/Wireframe.cpp
src/Wireframe.h
src/downcast.h
@@ -62,6 +63,7 @@ set(SRCS
src/extended/TangentSpaceMeshWrapper.h
)
# ==================================================================================================
# Build materials
# ==================================================================================================
@@ -181,6 +183,10 @@ set_target_properties(uberarchive PROPERTIES FOLDER Libs)
# Build the "core" library (no filamat)
# ==================================================================================================
if(FILAMENT_SUPPORTS_WEBP_TEXTURES)
set(WEBP_DECODER_LIB_TARGET webpdecoder)
endif()
include_directories(${PUBLIC_HDR_DIR} ${RESOURCE_DIR})
link_libraries(math utils filament cgltf stb ktxreader geometry tsl uberzlib)
@@ -190,7 +196,7 @@ target_include_directories(gltfio_core PUBLIC ${PUBLIC_HDR_DIR})
set_target_properties(gltfio_core PROPERTIES FOLDER Libs)
target_compile_definitions(gltfio_core PUBLIC -DGLTFIO_DRACO_SUPPORTED=1)
target_link_libraries(gltfio_core PUBLIC dracodec meshoptimizer)
target_link_libraries(gltfio_core PUBLIC dracodec meshoptimizer ${WEBP_DECODER_LIB_TARGET})
if (WEBGL_PTHREADS)
target_compile_definitions(gltfio_core PUBLIC -DFILAMENT_WASM_THREADS)
@@ -233,6 +239,7 @@ function(add_test_gltf SOURCE TARGET)
endfunction()
add_test_gltf("third_party/models/AnimatedMorphCube/AnimatedMorphCube.glb" "AnimatedMorphCube.glb")
add_test_gltf("third_party/models/DamagedHelmet/DamagedHelmetWebp.glb" "DamagedHelmetWebp.glb")
add_custom_target(test_gltfio_files DEPENDS ${GLTF_TEST_FILES})

View File

@@ -179,6 +179,19 @@ TextureProvider* createStbProvider(filament::Engine* engine);
*/
TextureProvider* createKtx2Provider(filament::Engine* engine);
/**
* If webp support is enabled at build time, creates a decoder that can handle "image/webp"
* lossless and lossy content.
* If webp support is not enabled at build time, returns nullptr.
*/
TextureProvider* createWebpProvider(filament::Engine* engine);
/**
* Indicates if build-time webp support was included.
* Returns true if it was and false if not.
*/
bool isWebpSupported();
} // namespace filament::gltfio
template<> struct utils::EnableBitMaskOperators<filament::gltfio::TextureProvider::TextureFlags>

View File

@@ -107,7 +107,7 @@ const char* FFilamentAsset::getExtras(utils::Entity entity) const noexcept {
void FFilamentAsset::addTextureBinding(MaterialInstance* materialInstance,
const char* parameterName, const cgltf_texture* srcTexture,
TextureProvider::TextureFlags flags) {
if (!srcTexture->image && !srcTexture->basisu_image) {
if (!srcTexture->image && !srcTexture->basisu_image && !srcTexture->webp_image) {
#ifndef NDEBUG
slog.w << "Texture is missing image (" << srcTexture->name << ")." << io::endl;
#endif

View File

@@ -525,7 +525,7 @@ void ResourceLoader::asyncUpdateLoad() {
std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilamentAsset* asset,
size_t textureIndex, TextureProvider::TextureFlags flags) {
const cgltf_texture& srcTexture = asset->mSourceAsset->hierarchy->textures[textureIndex];
const cgltf_image* image = srcTexture.basisu_image ?
const cgltf_image* image = srcTexture.webp_image ? srcTexture.webp_image : srcTexture.basisu_image ?
srcTexture.basisu_image : srcTexture.image;
if (!image) {

View File

@@ -0,0 +1,303 @@
/*
* 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 <gltfio/TextureProvider.h>
#if !defined(FILAMENT_SUPPORTS_WEBP_TEXTURES)
namespace filament::gltfio {
bool isWebpSupported() {
return false;
}
TextureProvider* createWebpProvider(Engine* engine) {
return nullptr;
}
}
#else // FILAMENT_SUPPORTS_WEBP_TEXTURES
#include <string>
#include <vector>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <filament/Engine.h>
#include <filament/Texture.h>
#include <webp/decode.h>
using namespace filament;
using namespace utils;
using std::atomic;
using std::vector;
using std::unique_ptr;
namespace filament::gltfio {
class WebpProvider final : public TextureProvider {
public:
WebpProvider(Engine* engine);
~WebpProvider();
Texture* pushTexture(const uint8_t* data, size_t byteCount,
const char* mimeType, TextureFlags flags) final;
Texture* popTexture() final;
void updateQueue() final;
void waitForCompletion() final;
void cancelDecoding() final;
const char* getPushMessage() const final;
const char* getPopMessage() const final;
size_t getPushedCount() const final { return mPushedCount; }
size_t getPoppedCount() const final { return mPoppedCount; }
size_t getDecodedCount() const final { return mDecodedCount; }
private:
enum class TextureState {
DECODING, // Texture has been pushed, mipmap levels are not yet complete.
READY, // Mipmap levels are available but texture has not been popped yet.
POPPED, // Client has popped the texture from the queue.
};
struct TextureInfo {
Texture* texture;
TextureState state;
atomic<intptr_t> decodedTexelsBaseMipmap;
vector<uint8_t> sourceBuffer;
JobSystem::Job* decoderJob;
};
// Declare some sentinel values for the "decodedTexelsBaseMipmap" field.
// Note that the "state" field can be modified only on the foreground thread.
static const intptr_t DECODING_NOT_READY = 0x0;
static const intptr_t DECODING_ERROR = 0x1;
void decodeSingleTexture();
size_t mPushedCount = 0;
size_t mPoppedCount = 0;
size_t mDecodedCount = 0;
vector<unique_ptr<TextureInfo> > mTextures;
JobSystem::Job* mDecoderRootJob;
std::string mRecentPushMessage;
std::string mRecentPopMessage;
Engine* const mEngine;
};
Texture* WebpProvider::pushTexture(const uint8_t* data, size_t byteCount,
const char* mimeType, TextureFlags flags) {
int width, height;
if (!WebPGetInfo(data, byteCount, &width, &height)) {
mRecentPushMessage = "Unable to parse webp texture";
return nullptr;
}
using InternalFormat = Texture::InternalFormat;
Texture* texture = Texture::Builder()
.width(width)
.height(height)
.levels(0xff)
.format(any(flags & TextureFlags::sRGB) ? InternalFormat::SRGB8_A8 : InternalFormat::RGBA8)
.usage(Texture::Usage::DEFAULT | Texture::Usage::GEN_MIPMAPPABLE)
.build(*mEngine);
if (texture == nullptr) {
mRecentPushMessage = "Unable to build Texture object for webp image.";
return nullptr;
}
mRecentPushMessage.clear();
TextureInfo* info = mTextures.emplace_back(new TextureInfo).get();
++mPushedCount;
info->texture = texture;
info->state = TextureState::DECODING;
info->sourceBuffer.assign(data, data + byteCount);
info->decodedTexelsBaseMipmap.store(DECODING_NOT_READY);
// On single threaded systems, it is usually fine to create jobs because the job system will
// simply execute serially. However in our case, we wish to amortize the decoder cost across
// several frames, so we instead use the updateQueue() method to perform decoding.
if constexpr (!UTILS_HAS_THREADING) {
info->decoderJob = nullptr;
return texture;
}
JobSystem* js = &mEngine->getJobSystem();
info->decoderJob = jobs::createJob(*js, mDecoderRootJob, [info] {
auto& source = info->sourceBuffer;
int width, height;
uint8_t* texels = WebPDecodeRGBA(source.data(), source.size(), &width, &height);
// Test asynchronous loading by uncommenting this line.
// std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10000));
source.clear();
source.shrink_to_fit();
info->decodedTexelsBaseMipmap.store(texels ? intptr_t(texels) : DECODING_ERROR);
});
js->runAndRetain(info->decoderJob);
return texture;
}
Texture* WebpProvider::popTexture() {
// We don't bother shrinking the mTextures vector here, instead we periodically clean it up in
// the updateQueue method, since popTexture is typically called more frequently. Textures
// can become ready in non-deterministic order due to concurrency.
for (auto& texture : mTextures) {
if (texture->state == TextureState::READY) {
texture->state = TextureState::POPPED;
++mPoppedCount;
const intptr_t ptr = texture->decodedTexelsBaseMipmap.load();
if (ptr == DECODING_ERROR || ptr == DECODING_NOT_READY) {
mRecentPopMessage = "Texture is incomplete";
} else {
mRecentPopMessage.clear();
}
return texture->texture;
}
}
return nullptr;
}
void WebpProvider::updateQueue() {
if (!UTILS_HAS_THREADING) {
decodeSingleTexture();
}
JobSystem* js = &mEngine->getJobSystem();
for (auto& info : mTextures) {
if (info->state != TextureState::DECODING) {
continue;
}
Texture* texture = info->texture;
if (intptr_t data = info->decodedTexelsBaseMipmap.load()) {
if (info->decoderJob) {
js->waitAndRelease(info->decoderJob);
}
if (data == DECODING_ERROR) {
info->state = TextureState::READY;
++mDecodedCount;
continue;
}
Texture::PixelBufferDescriptor pbd((uint8_t*) data,
texture->getWidth() * texture->getHeight() * 4, Texture::Format::RGBA,
Texture::Type::UBYTE, [](void* mem, size_t, void*) { WebPFree(mem); });
texture->setImage(*mEngine, 0, std::move(pbd));
// Call generateMipmaps unconditionally to fulfill the promise of the TextureProvider
// interface. Providers of hierarchical images (e.g. KTX) call this only if needed.
texture->generateMipmaps(*mEngine);
info->state = TextureState::READY;
++mDecodedCount;
}
}
// Here we periodically clean up the "queue" (which is really just a vector) by removing unused
// items from the front. This might ignore a popped texture that occurs in the middle of the
// vector, but that's okay, it will be cleaned up eventually.
decltype(mTextures)::iterator last = mTextures.begin();
while (last != mTextures.end() && (*last)->state == TextureState::POPPED) ++last;
mTextures.erase(mTextures.begin(), last);
}
void WebpProvider::waitForCompletion() {
JobSystem& js = mEngine->getJobSystem();
for (auto& info : mTextures) {
if (info->decoderJob) {
js.waitAndRelease(info->decoderJob);
}
}
}
void WebpProvider::cancelDecoding() {
// TODO: Currently, WebpProvider runs jobs eagerly and JobSystem does not allow cancellation of
// in-flight jobs. We should consider throttling the number of simultaneous decoder jobs, which
// would allow for actual cancellation.
waitForCompletion();
// For cancelled jobs, we need to set the TextureInfo to the popped state and free the decoded
// data.
for (auto& info : mTextures) {
if (info->state != TextureState::DECODING) {
continue;
}
// Deleting data here should be safe thread-wise as the only other place where
// decodedTexelsBaseMipmap is loaded is in the job threads, and we have waited them to
// completion above. We also expect the TextureProvider API calls to be made only from one
// thread.
if (auto data = (void*)info->decodedTexelsBaseMipmap.load()) {
WebPFree(data);
}
info->state = TextureState::POPPED;
}
}
const char* WebpProvider::getPushMessage() const {
return mRecentPushMessage.empty() ? nullptr : mRecentPushMessage.c_str();
}
const char* WebpProvider::getPopMessage() const {
return mRecentPopMessage.empty() ? nullptr : mRecentPopMessage.c_str();
}
void WebpProvider::decodeSingleTexture() {
assert_invariant(!UTILS_HAS_THREADING);
for (auto& info : mTextures) {
if (info->state == TextureState::DECODING) {
auto& source = info->sourceBuffer;
int width, height;
uint8_t* texels = WebPDecodeRGBA(source.data(), source.size(), &width, &height);
source.clear();
source.shrink_to_fit();
info->decodedTexelsBaseMipmap.store(texels ? intptr_t(texels) : DECODING_ERROR);
break;
}
}
}
WebpProvider::WebpProvider(Engine* engine) : mEngine(engine) {
mDecoderRootJob = mEngine->getJobSystem().createJob();
#ifndef NDEBUG
slog.i << "Texture Decoder has "
<< mEngine->getJobSystem().getThreadCount()
<< " background threads." << io::endl;
#endif
}
WebpProvider::~WebpProvider() {
cancelDecoding();
mEngine->getJobSystem().release(mDecoderRootJob);
}
bool isWebpSupported() {
return true;
}
TextureProvider* createWebpProvider(Engine* engine) {
return new WebpProvider(engine);
}
} // namespace filament::gltfio
#endif // FILAMENT_SUPPORTS_WEBP_TEXTURES

View File

@@ -44,6 +44,7 @@ using namespace gltfio;
using namespace utils;
char const* ANIMATED_MORPH_CUBE_GLB = "AnimatedMorphCube.glb";
char const* DAMAGED_HELMET_WEBP_GLB = "DamagedHelmetWebp.glb";
static std::ifstream::pos_type getFileSize(const char* filename) {
std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary);
@@ -58,9 +59,13 @@ public:
mResourceLoader(new ResourceLoader({
engine, filename.getAbsolutePath().c_str(), false, /* normalizeSkinningWeights */
})),
mStbDecoder(createStbProvider(engine)), mKtxDecoder(createKtx2Provider(engine)) {
mStbDecoder(createStbProvider(engine)), mKtxDecoder(createKtx2Provider(engine)),
mWebpDecoder(createWebpProvider(engine)) {
mResourceLoader->addTextureProvider("image/png", mStbDecoder);
mResourceLoader->addTextureProvider("image/ktx2", mKtxDecoder);
if (mWebpDecoder) {
mResourceLoader->addTextureProvider("image/webp", mWebpDecoder);
}
long contentSize = static_cast<long>(getFileSize(filename.c_str()));
if (contentSize <= 0) {
@@ -99,6 +104,7 @@ public:
delete mResourceLoader;
delete mStbDecoder;
delete mKtxDecoder;
delete mWebpDecoder;
AssetLoader::destroy(&mAssetLoader);
}
@@ -109,6 +115,7 @@ public:
ResourceLoader* mResourceLoader = nullptr;
TextureProvider* mStbDecoder = nullptr;
TextureProvider* mKtxDecoder = nullptr;
TextureProvider* mWebpDecoder = nullptr;
FilamentAsset* mAsset = nullptr;
};
@@ -128,7 +135,7 @@ protected:
mMaterialProvider = createUbershaderProvider(mEngine, UBERARCHIVE_DEFAULT_DATA,
UBERARCHIVE_DEFAULT_SIZE);
for (auto fname: {ANIMATED_MORPH_CUBE_GLB}) {
for (auto fname: {ANIMATED_MORPH_CUBE_GLB, DAMAGED_HELMET_WEBP_GLB}) {
Path gltfFile = Path::getCurrentExecutable().getParent() + Path(fname);
mData[fname] =
std::make_unique<glTFData>(gltfFile, mEngine, mMaterialProvider, mNameManager);
@@ -236,6 +243,26 @@ TEST_F(glTFIOTest, AnimatedMorphCubeRenderables) {
EXPECT_EQ(morphTargetBuffer->getVertexCount(), 24u);
}
TEST_F(glTFIOTest, DamagedHelmetWebpMaterials) {
FilamentAsset const& damagedHelmetAsset = *mData[DAMAGED_HELMET_WEBP_GLB]->getAsset();
Entity const* renderables = damagedHelmetAsset.getRenderableEntities();
auto& renderableManager = mEngine->getRenderableManager();
auto inst = renderableManager.getInstance(renderables[0]);
auto materialInst = renderableManager.getMaterialInstanceAt(inst, 0);
std::string_view name{materialInst->getName()};
EXPECT_EQ(name, "Material_MR");
#if defined(FILAMENT_SUPPORTS_WEBP_TEXTURES)
EXPECT_TRUE(isWebpSupported());
EXPECT_FALSE(mData[DAMAGED_HELMET_WEBP_GLB]->mWebpDecoder == nullptr);
EXPECT_EQ(mEngine->getTextureCount(), 8);
#else
EXPECT_FALSE(isWebpSupported());
EXPECT_TRUE(mData[DAMAGED_HELMET_WEBP_GLB]->mWebpDecoder == nullptr);
EXPECT_EQ(mEngine->getTextureCount(), 3);
#endif
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();

View File

@@ -73,6 +73,7 @@ struct App {
ResourceLoader* resourceLoader = nullptr;
gltfio::TextureProvider* stbDecoder = nullptr;
gltfio::TextureProvider* ktxDecoder = nullptr;
gltfio::TextureProvider* webpDecoder = nullptr;
int instanceToAnimate = -1;
std::vector<FilamentInstance*> instances;
};
@@ -213,6 +214,13 @@ int main(int argc, char** argv) {
app.resourceLoader->addTextureProvider("image/png", app.stbDecoder);
app.resourceLoader->addTextureProvider("image/jpeg", app.stbDecoder);
app.resourceLoader->addTextureProvider("image/ktx2", app.ktxDecoder);
if (isWebpSupported()) {
app.webpDecoder = createWebpProvider(app.engine);
app.resourceLoader->addTextureProvider("image/webp", app.webpDecoder);
}
else {
app.webpDecoder = nullptr;
}
}
if (!app.resourceLoader->asyncBeginLoad(app.asset)) {
@@ -280,6 +288,7 @@ int main(int argc, char** argv) {
delete app.resourceLoader;
delete app.stbDecoder;
delete app.ktxDecoder;
delete app.webpDecoder;
AssetLoader::destroy(&app.loader);
};

View File

@@ -109,6 +109,7 @@ struct App {
gltfio::ResourceLoader* resourceLoader = nullptr;
gltfio::TextureProvider* stbDecoder = nullptr;
gltfio::TextureProvider* ktxDecoder = nullptr;
gltfio::TextureProvider* webpDecoder = nullptr;
bool recomputeAabb = false;
bool actualSize = false;
@@ -699,6 +700,12 @@ int main(int argc, char** argv) {
app.resourceLoader->addTextureProvider("image/png", app.stbDecoder);
app.resourceLoader->addTextureProvider("image/jpeg", app.stbDecoder);
app.resourceLoader->addTextureProvider("image/ktx2", app.ktxDecoder);
if (isWebpSupported()) {
app.webpDecoder = createWebpProvider(app.engine);
app.resourceLoader->addTextureProvider("image/webp", app.webpDecoder);
} else {
app.webpDecoder = nullptr;
}
} else {
app.resourceLoader->setConfiguration(configuration);
}
@@ -1061,6 +1068,7 @@ int main(int argc, char** argv) {
delete app.resourceLoader;
delete app.stbDecoder;
delete app.ktxDecoder;
delete app.webpDecoder;
delete app.automationSpec;
delete app.automationEngine;

Binary file not shown.

View File

@@ -13,3 +13,10 @@ https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
## Modifications
The original model was built on an early draft of glTF 2.0 that did not become final. This new model has been imported and re-exported from Blender to bring it into alignment with the final release glTF 2.0 specification.
## Damaged Helmet WebP
* Derived from DamagedHelmet.glb described above
* Images converted to webp
* Textures updated to use EXT_texture_webp extension
* geometry draco compressed
* Modifications done via [gltf-transform](https://gltf-transform.dev/) CLI utility

View File

@@ -2046,6 +2046,7 @@ struct UbershaderProvider {
struct StbProvider { TextureProvider* provider; };
struct Ktx2Provider { TextureProvider* provider; };
struct WebpProvider { TextureProvider* provider; };
class_<UbershaderProvider>("gltfio$UbershaderProvider")
.constructor(EMBIND_LAMBDA(UbershaderProvider, (Engine* engine), {
@@ -2064,6 +2065,12 @@ class_<Ktx2Provider>("gltfio$Ktx2Provider")
return Ktx2Provider { createKtx2Provider(engine) };
}));
class_<WebpProvider>("gltfio$WebpProvider")
.constructor(EMBIND_LAMBDA(WebpProvider, (Engine* engine), {
return WebpProvider { createWebpProvider(engine) };
}))
.class_function("isWebpSupported", &isWebpSupported);
class_<AssetLoader>("gltfio$AssetLoader")
.constructor(EMBIND_LAMBDA(AssetLoader*, (Engine* engine, UbershaderProvider materials), {
@@ -2124,6 +2131,13 @@ class_<ResourceLoader>("gltfio$ResourceLoader")
self->addTextureProvider(mime.c_str(), provider.provider);
}), allow_raw_pointers())
.function("addWebpProvider", EMBIND_LAMBDA(void, (ResourceLoader* self, std::string mime,
WebpProvider provider), {
if (provider.provider) {
self->addTextureProvider(mime.c_str(), provider.provider);
}
}), allow_raw_pointers())
.function("hasResourceData", EMBIND_LAMBDA(bool, (ResourceLoader* self, std::string url), {
return self->hasResourceData(url.c_str());
}), allow_raw_pointers())