GLTFIO Webp Texture Support (#9406)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
303
libs/gltfio/src/WebpProvider.cpp
Normal file
303
libs/gltfio/src/WebpProvider.cpp
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
BIN
third_party/models/DamagedHelmet/DamagedHelmetWebp.glb
vendored
Normal file
BIN
third_party/models/DamagedHelmet/DamagedHelmetWebp.glb
vendored
Normal file
Binary file not shown.
7
third_party/models/DamagedHelmet/README.md
vendored
7
third_party/models/DamagedHelmet/README.md
vendored
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user