Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEW_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
- engine: Optimize Color Grading with NEON on armv8+ devices. Performance improvements between 1.3x and 4.5x
- New `coloredPenumbra` material property can be used to simulate light scattering in shadow
transitions. See Filament's material guide for more information
- gltfio: Support textures bound to both sRGB and Linear parameters
80 changes: 73 additions & 7 deletions libs/gltfio/src/FFilamentAsset.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@

#include <math/mat4.h>

#include <utils/FixedCapacityVector.h>
#include <utils/CString.h>
#include <utils/Entity.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Hash.h>

#include <cgltf.h>

Expand Down Expand Up @@ -117,6 +118,13 @@ struct FFilamentAsset : public FilamentAsset {
mSourceAsset(new SourceAsset {(cgltf_data*)srcAsset}),
mTextures(srcAsset->textures_count),
mMeshCache(srcAsset->meshes_count) {
// Initialize mTextures with default slots, one per cgltf_texture index.
for (size_t i = 0; i < srcAsset->textures_count; ++i) {
mTextures[i].gltfTextureIndex = i;
mTextures[i].texture = nullptr;
mTextures[i].isOwner = false;
mTextures[i].flags = TextureProvider::TextureFlags::NONE;
}
if (!useExtendedAlgo) {
mResourceInfo = ResourceInfo{};
} else {
Expand Down Expand Up @@ -303,19 +311,58 @@ struct FFilamentAsset : public FilamentAsset {
// The mapping of root nodes to scene membership sets.
tsl::robin_map<cgltf_node*, SceneMask> mRootNodes;

// Stores all information related to a single cgltf_texture.
// Note that more than one cgltf_texture can map to a single Filament texture,
// e.g. if several have the same URL or bufferView. For each Filament texture,
// only one of its corresponding TextureInfo slots will have isOwner=true.
struct TextureInfo {
std::vector<TextureSlot> bindings;
Texture* texture;
TextureProvider::TextureFlags flags;
bool isOwner;
size_t gltfTextureIndex;
};

// Mapping from cgltf_texture to Texture* is required when creating new instances.
utils::FixedCapacityVector<TextureInfo> mTextures;
// Stores all information related to a single cgltf_texture or split textures under different
// color spaces. There are two different texture index namespaces:
// - gltfTextureIndex: The texture index defined in the original glTF file.
// - assetTextureIndex: The unique index in the mTextures vector representing a resolved,
// unique Filament Texture resource.
// A cgltf_texture can map to multiple asset textures (Filament textures) if they have different
// color space flags (sRGB vs Linear). Note that more than one cgltf_texture can map to a single
// Filament texture, e.g. if several have the same URL or bufferView. For each Filament texture,
// only one of its corresponding TextureInfo slots will have isOwner=true.
std::vector<TextureInfo> mTextures;

/**
* Obtains the resolved assetTextureIndex for a given gltfTextureIndex and flags.
*
* If it is the first binding for this gltfTextureIndex, it assigns the flags to the default
* slot and returns it. If it is bound with different flags later, it allocates a new duplicate
* slot at the end of mTextures.
*/
size_t obtainAssetTextureIndex(size_t gltfTextureIndex, TextureProvider::TextureFlags flags) {
auto key = TextureKey{ gltfTextureIndex, flags };
auto it = mResolvedTextures.find(key);
if (it != mResolvedTextures.end()) {
return it->second;
}

// First check if the default texture slot has been assigned.
TextureInfo& defaultSlot = mTextures[gltfTextureIndex];
if (defaultSlot.bindings.empty() && defaultSlot.texture == nullptr) {
defaultSlot.flags = flags;
mResolvedTextures[key] = gltfTextureIndex;
return gltfTextureIndex;
}

// At this point, gltfTextureIndex is being used with different flags, so we allocate a new
// slot for it at the end of the mTextures vector.
mTextures.push_back(TextureInfo{ .bindings = {},
.texture = nullptr,
.flags = flags,
.isOwner = false,
.gltfTextureIndex = gltfTextureIndex });
size_t newIndex = mTextures.size() - 1;
mResolvedTextures[key] = newIndex;
return newIndex;
}

// Resource URIs can be queried by the end user.
utils::FixedCapacityVector<const char*> mResourceUris;
Expand Down Expand Up @@ -372,6 +419,25 @@ struct FFilamentAsset : public FilamentAsset {
};

std::variant<ResourceInfo, ResourceInfoExtended> mResourceInfo;

private:
struct TextureKey {
size_t gltfTextureIndex;
TextureProvider::TextureFlags flags;
bool operator==(const TextureKey& o) const {
return gltfTextureIndex == o.gltfTextureIndex && flags == o.flags;
}
};
struct TextureKeyHash {
size_t operator()(const TextureKey& k) const {
size_t seed = 0;
utils::hash::combine(seed, k.gltfTextureIndex);
utils::hash::combine(seed, (uint64_t) k.flags);
return seed;
}
};
// Lookup map cache mapping (gltfTextureIndex, flags) to its resolved assetTextureIndex.
std::unordered_map<TextureKey, size_t, TextureKeyHash> mResolvedTextures;
};

FILAMENT_DOWNCAST(FilamentAsset)
Expand Down
18 changes: 8 additions & 10 deletions libs/gltfio/src/FilamentAsset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,27 +114,25 @@ void FFilamentAsset::addTextureBinding(MaterialInstance* materialInstance,
return;
}

const size_t textureIndex = (size_t) (srcTexture - mSourceAsset->hierarchy->textures);
TextureInfo& info = mTextures[textureIndex];

// All bindings for a particular glTF texture must have the same transform function.
assert_invariant(info.bindings.size() == 0 || info.flags == flags);
info.flags = flags;
const size_t gltfTextureIndex = (size_t) (srcTexture - mSourceAsset->hierarchy->textures);
const size_t assetTextureIndex = obtainAssetTextureIndex(gltfTextureIndex, flags);
TextureInfo& info = mTextures[assetTextureIndex];

const TextureSlot slot = { materialInstance, parameterName };
if (info.texture) {
applyTextureBinding(textureIndex, slot, false);
applyTextureBinding(assetTextureIndex, slot, false);
} else {
mDependencyGraph.addEdge(materialInstance, parameterName);
info.bindings.push_back(slot);
}
}

void FFilamentAsset::applyTextureBinding(size_t textureIndex, const TextureSlot& tb,
void FFilamentAsset::applyTextureBinding(size_t assetTextureIndex, const TextureSlot& tb,
bool addDependency) {
const TextureInfo& info = mTextures[textureIndex];
const TextureInfo& info = mTextures[assetTextureIndex];
assert_invariant(info.texture);
const cgltf_sampler* srcSampler = mSourceAsset->hierarchy->textures[textureIndex].sampler;
const cgltf_sampler* srcSampler =
mSourceAsset->hierarchy->textures[info.gltfTextureIndex].sampler;
TextureSampler sampler;
if (srcSampler) {
sampler.setWrapModeS(getWrapMode(srcSampler->wrap_s));
Expand Down
43 changes: 27 additions & 16 deletions libs/gltfio/src/ResourceLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ struct ResourceLoader::Impl {
TextureProviderList mTextureProviders;

// Avoid duplicated Texture objects via caches with two key types: buffer pointers and strings.
BufferTextureCache mBufferTextureCache;
FilepathTextureCache mFilepathTextureCache;
// We use two of each cache (indexed by (uint64_t)flags & 1) to prevent color space collisions
// (sRGB vs Linear) when the same image source is bound to multiple parameters.
BufferTextureCache mBufferTextureCache[2];
FilepathTextureCache mFilepathTextureCache[2];

FFilamentAsset* mAsyncAsset = nullptr;
size_t mRemainingTextureDownloads = 0;
Expand Down Expand Up @@ -701,8 +703,10 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {

// Clear our texture caches. Previous calls to loadResources may have populated these, but the
// Texture objects could have since been destroyed.
pImpl->mBufferTextureCache.clear();
pImpl->mFilepathTextureCache.clear();
pImpl->mBufferTextureCache[0].clear();
pImpl->mBufferTextureCache[1].clear();
pImpl->mFilepathTextureCache[0].clear();
pImpl->mFilepathTextureCache[1].clear();

cgltf_data const* gltf = asset->mSourceAsset->hierarchy;

Expand Down Expand Up @@ -834,17 +838,20 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
TextureProvider* provider = foundProvider->second;
assert_invariant(provider);

const size_t cacheIdx = any(flags & TextureProvider::TextureFlags::sRGB) ? 1 : 0;

// Check if the texture slot uses BufferView data.
if (void** bufferViewData = bv ? &bv->buffer->data : nullptr; bufferViewData) {
assert_invariant(!dataUriContent);
const size_t offset = bv ? bv->offset : 0;
const uint8_t* sourceData = offset + (const uint8_t*) *bufferViewData;
if (auto iter = mBufferTextureCache.find(sourceData); iter != mBufferTextureCache.end()) {
if (auto iter = mBufferTextureCache[cacheIdx].find(sourceData);
iter != mBufferTextureCache[cacheIdx].end()) {
return {iter->second, CacheResult::FOUND};
}
const uint32_t totalSize = uint32_t(bv ? bv->size : 0);
if (Texture* texture = provider->pushTexture(sourceData, totalSize, mime.c_str(), flags); texture) {
mBufferTextureCache[sourceData] = texture;
mBufferTextureCache[cacheIdx][sourceData] = texture;
return {texture, CacheResult::MISS};
}
}
Expand All @@ -853,13 +860,14 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
// Note that this is a data URI in an image, not a buffer. Data URI's in buffers are decoded
// by the cgltf_load_buffers() function.
else if (dataUriContent) {
if (auto iter = mBufferTextureCache.find(uri); iter != mBufferTextureCache.end()) {
if (auto iter = mBufferTextureCache[cacheIdx].find(uri);
iter != mBufferTextureCache[cacheIdx].end()) {
free((void*)dataUriContent);
return {iter->second, CacheResult::FOUND};
}
if (Texture* texture = provider->pushTexture(dataUriContent, dataUriSize, mime.c_str(), flags); texture) {
free((void*)dataUriContent);
mBufferTextureCache[uri] = texture;
mBufferTextureCache[cacheIdx][uri] = texture;
return {texture, CacheResult::MISS};
}
free((void*)dataUriContent);
Expand All @@ -868,18 +876,20 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
// Check the user-supplied resource cache for this URI.
else if (auto iter = mUriDataCache->find(uri); iter != mUriDataCache->end()) {
const uint8_t* sourceData = (const uint8_t*) iter->second.buffer;
if (auto iter = mBufferTextureCache.find(sourceData); iter != mBufferTextureCache.end()) {
if (auto iter = mBufferTextureCache[cacheIdx].find(sourceData);
iter != mBufferTextureCache[cacheIdx].end()) {
return {iter->second, CacheResult::FOUND};
}
if (Texture* texture = provider->pushTexture(sourceData, iter->second.size, mime.c_str(), flags); texture) {
mBufferTextureCache[sourceData] = texture;
mBufferTextureCache[cacheIdx][sourceData] = texture;
return {texture, CacheResult::MISS};
}
}

// Finally, try the file system.
else if constexpr (GLTFIO_USE_FILESYSTEM) {
if (auto iter = mFilepathTextureCache.find(uri); iter != mFilepathTextureCache.end()) {
if (auto iter = mFilepathTextureCache[cacheIdx].find(uri);
iter != mFilepathTextureCache[cacheIdx].end()) {
return {iter->second, CacheResult::FOUND};
}
Path fullpath = Path(mGltfPath).getParent() + uri;
Expand All @@ -895,7 +905,7 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
filest.seekg(0, ios::beg);
buffer.assign((istreambuf_iterator<char>(filest)), istreambuf_iterator<char>());
if (Texture* texture = provider->pushTexture(buffer.data(), buffer.size(), mime.c_str(), flags); texture) {
mFilepathTextureCache[uri] = texture;
mFilepathTextureCache[cacheIdx][uri] = texture;
return {texture, CacheResult::MISS};
}

Expand All @@ -921,9 +931,10 @@ void ResourceLoader::Impl::createTextures(FFilamentAsset* asset, bool async) {
mRemainingTextureDownloads = 0;

// Create new texture objects if they are not cached and kick off decoding jobs.
for (size_t textureIndex = 0, n = asset->mTextures.size(); textureIndex < n; ++textureIndex) {
FFilamentAsset::TextureInfo& info = asset->mTextures[textureIndex];
auto [texture, cacheResult] = getOrCreateTexture(asset, textureIndex, info.flags);
for (size_t assetTextureIndex = 0, n = asset->mTextures.size(); assetTextureIndex < n;
++assetTextureIndex) {
FFilamentAsset::TextureInfo& info = asset->mTextures[assetTextureIndex];
auto [texture, cacheResult] = getOrCreateTexture(asset, info.gltfTextureIndex, info.flags);
if (texture == nullptr) {
if (cacheResult == CacheResult::NOT_READY) {
mRemainingTextureDownloads++;
Expand All @@ -940,7 +951,7 @@ void ResourceLoader::Impl::createTextures(FFilamentAsset* asset, bool async) {

// For each binding to a material instance, call setParameter(...) on the material.
for (const TextureSlot& slot : info.bindings) {
asset->applyTextureBinding(textureIndex, slot);
asset->applyTextureBinding(assetTextureIndex, slot);
}
}

Expand Down
Loading