/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "FFilamentAsset.h" #include "upcast.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__EMSCRIPTEN__) || defined(ANDROID) #define USE_FILESYSTEM 0 #else #define USE_FILESYSTEM 1 #include #endif using namespace filament; using namespace filament::math; using namespace utils; static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { free(mem); }; namespace { struct TextureCacheEntry { Texture* texture; std::atomic texels; uint32_t bufferSize; int width; int height; int numComponents; bool srgb; bool completed; }; using BufferTextureCache = tsl::robin_map>; using UriTextureCache = tsl::robin_map>; using UriDataCache = tsl::robin_map; } namespace gltfio { struct ResourceLoader::Impl { Impl(const ResourceConfiguration& config) { mGltfPath = std::string(config.gltfPath ? config.gltfPath : ""); mEngine = config.engine; mNormalizeSkinningWeights = config.normalizeSkinningWeights; mRecomputeBoundingBoxes = config.recomputeBoundingBoxes; } Engine* mEngine; bool mNormalizeSkinningWeights; bool mRecomputeBoundingBoxes; std::string mGltfPath; // User-provided resource data with URI string keys, populated with addResourceData(). // This is used on platforms without traditional file systems, such as Android and WebGL. UriDataCache mUriDataCache; // The two texture caches are populated while textures are being decoded, and they are no longer // used after all textures have been finalized. Since multiple glTF textures might be loaded // from a single URI or buffer pointer, these caches prevent needless re-decoding. There are // two caches: one for URI-based textures and one for buffer-based textures. BufferTextureCache mBufferTextureCache; UriTextureCache mUriTextureCache; int mNumDecoderTasks; int mNumDecoderTasksFinished; JobSystem::Job* mDecoderRootJob = nullptr; FFilamentAsset* mCurrentAsset; void computeTangents(FFilamentAsset* asset); bool createTextures(bool async); void cancelTextureDecoding(); void addTextureCacheEntry(const TextureSlot& tb); void bindTextureToMaterial(const TextureSlot& tb); void decodeSingleTexture(); void uploadPendingTextures(); void releasePendingTextures(); ~Impl(); }; uint32_t computeBindingSize(const cgltf_accessor* accessor); uint32_t computeBindingOffset(const cgltf_accessor* accessor); // The AssetPool tracks references to raw source data (cgltf hierarchies) and frees them // appropriately. It releases all source assets only after the pending upload count is zero and the // client has destroyed the ResourceLoader object. If the ResourceLoader is destroyed while uploads // are still pending, then the AssetPool will stay alive until all uploads are complete. class AssetPool { public: AssetPool() {} ~AssetPool() { for (auto asset : mAssets) { asset->releaseSourceAsset(); } } void addAsset(FFilamentAsset* asset) { mAssets.push_back(asset); asset->acquireSourceAsset(); } void addPendingUpload() { ++mPendingUploads; } static void onLoadedResource(void* buffer, size_t size, void* user) { auto pool = (AssetPool*) user; if (--pool->mPendingUploads == 0 && pool->mLoaderDestroyed) { delete pool; } } void onLoaderDestroyed() { if (mPendingUploads == 0) { delete this; } else { mLoaderDestroyed = true; } } private: std::vector mAssets; bool mLoaderDestroyed = false; int mPendingUploads = 0; }; static void importSkins(const cgltf_data* gltf, const NodeMap& nodeMap, SkinVector& dstSkins) { dstSkins.resize(gltf->skins_count); for (cgltf_size i = 0, len = gltf->nodes_count; i < len; ++i) { const cgltf_node& node = gltf->nodes[i]; if (node.skin) { int skinIndex = node.skin - &gltf->skins[0]; Entity entity = nodeMap.at(&node); dstSkins[skinIndex].targets.push_back(entity); } } for (cgltf_size i = 0, len = gltf->skins_count; i < len; ++i) { Skin& dstSkin = dstSkins[i]; const cgltf_skin& srcSkin = gltf->skins[i]; if (srcSkin.name) { dstSkin.name = srcSkin.name; } // Build a list of transformables for this skin, one for each joint. // TODO: We've seen models with joint nodes that do not belong to the scene's node graph. // e.g. BrainStem after Draco compression. That's why we have a fallback here. AssetManager // should maybe create an Entity for every glTF node, period. (regardless of hierarchy) // https://github.com/CesiumGS/gltf-pipeline/issues/532 dstSkin.joints.resize(srcSkin.joints_count); for (cgltf_size i = 0, len = srcSkin.joints_count; i < len; ++i) { auto iter = nodeMap.find(srcSkin.joints[i]); if (iter == nodeMap.end()) { dstSkin.joints[i] = nodeMap.begin()->second; } else { dstSkin.joints[i] = iter->second; } } // Retain a copy of the inverse bind matrices because the source blob could be evicted later. const cgltf_accessor* srcMatrices = srcSkin.inverse_bind_matrices; dstSkin.inverseBindMatrices.resize(srcSkin.joints_count); if (srcMatrices) { auto dstMatrices = (uint8_t*) dstSkin.inverseBindMatrices.data(); uint8_t* bytes = (uint8_t*) srcMatrices->buffer_view->buffer->data; auto srcBuffer = (void*) (bytes + srcMatrices->offset + srcMatrices->buffer_view->offset); memcpy(dstMatrices, srcBuffer, srcSkin.joints_count * sizeof(mat4f)); } } } static void convertBytesToShorts(uint16_t* dst, const uint8_t* src, size_t count) { for (size_t i = 0; i < count; ++i) { dst[i] = src[i]; } } static void decodeDracoMeshes(FFilamentAsset* asset) { DracoCache* dracoCache = &asset->mDracoCache; // For a given primitive and attribute, find the corresponding accessor. auto findAccessor = [](const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int idx) { for (cgltf_size i = 0; i < prim->attributes_count; i++) { const cgltf_attribute& attr = prim->attributes[i]; if (attr.type == type && attr.index == idx) { return attr.data; } } return (cgltf_accessor*) nullptr; }; // Go through every primitive and check if it has a Draco mesh. for (auto pair : asset->mPrimitives) { const cgltf_primitive* prim = pair.first; VertexBuffer* vb = pair.second; if (!prim->has_draco_mesh_compression) { continue; } const cgltf_draco_mesh_compression& draco = prim->draco_mesh_compression; // Check if we have already decoded this mesh. DracoMesh* mesh = dracoCache->findOrCreateMesh(draco.buffer_view); if (!mesh) { slog.w << "Cannot decompress mesh, Draco decoding error." << io::endl; continue; } // Copy over the decompressed data, converting the data type if necessary. if (prim->indices) { mesh->getFaceIndices(prim->indices); } // Go through each attribute in the decompressed mesh. for (cgltf_size i = 0; i < draco.attributes_count; i++) { // In cgltf, each Draco attribute's data pointer is an attribute id, not an accessor. const uint32_t id = draco.attributes[i].data - asset->mSourceAsset->accessors; // Find the destination accessor; this contains the desired component type, etc. const cgltf_attribute_type type = draco.attributes[i].type; const cgltf_int index = draco.attributes[i].index; cgltf_accessor* accessor = findAccessor(prim, type, index); if (!accessor) { slog.w << "Cannot find matching accessor for Draco id " << id << io::endl; continue; } // Copy over the decompressed data, converting the data type if necessary. mesh->getVertexAttributes(id, accessor); } } } ResourceLoader::ResourceLoader(const ResourceConfiguration& config) : mPool(new AssetPool), pImpl(new Impl(config)) { } ResourceLoader::~ResourceLoader() { mPool->onLoaderDestroyed(); delete pImpl; } void ResourceLoader::addResourceData(const char* uri, BufferDescriptor&& buffer) { // Start an async marker the first time this is called and end it when // finalization begins. This marker provides a rough indicator of how long // the client is taking to load raw data blobs from storage. if (pImpl->mUriDataCache.empty()) { SYSTRACE_CONTEXT(); SYSTRACE_ASYNC_BEGIN("addResourceData", 1); } pImpl->mUriDataCache.emplace(uri, std::move(buffer)); } bool ResourceLoader::hasResourceData(const char* uri) const { return pImpl->mUriDataCache.find(uri) != pImpl->mUriDataCache.end(); } bool ResourceLoader::loadResources(FilamentAsset* asset) { FFilamentAsset* fasset = upcast(asset); return loadResources(fasset, false); } bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { SYSTRACE_CONTEXT(); SYSTRACE_ASYNC_END("addResourceData", 1); if (asset->mResourcesLoaded) { return false; } asset->mResourcesLoaded = true; mPool->addAsset(asset); const cgltf_data* gltf = asset->mSourceAsset; cgltf_options options {}; // For emscripten and Android builds we have a custom implementation of cgltf_load_buffers which // looks inside a cache of externally-supplied data blobs, rather than loading from the // filesystem. SYSTRACE_NAME_BEGIN("Load buffers"); #if !USE_FILESYSTEM if (gltf->buffers_count && !gltf->buffers[0].data && !gltf->buffers[0].uri && gltf->bin) { if (gltf->bin_size < gltf->buffers[0].size) { slog.e << "Bad size." << io::endl; return false; } gltf->buffers[0].data = (void*) gltf->bin; } bool missingResources = false; for (cgltf_size i = 0; i < gltf->buffers_count; ++i) { if (gltf->buffers[i].data) { continue; } const char* uri = gltf->buffers[i].uri; if (uri == nullptr) { continue; } if (strncmp(uri, "data:", 5) == 0) { const char* comma = strchr(uri, ','); if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) { cgltf_result res = cgltf_load_buffer_base64(&options, gltf->buffers[i].size, comma + 1, &gltf->buffers[i].data); if (res != cgltf_result_success) { slog.e << "Unable to load " << uri << io::endl; return false; } } else { slog.e << "Unable to load " << uri << io::endl; return false; } } else if (strstr(uri, "://") == nullptr) { auto iter = pImpl->mUriDataCache.find(uri); if (iter == pImpl->mUriDataCache.end()) { slog.e << "Unable to load external resource: " << uri << io::endl; missingResources = true; } // Make a copy to allow cgltf_free() to work as expected and prevent a double-free. // TODO: Future versions of CGLTF will make this easier, see the following ticket. // https://github.com/jkuhlmann/cgltf/issues/94 gltf->buffers[i].data = malloc(iter->second.size); memcpy(gltf->buffers[i].data, iter->second.buffer, iter->second.size); } else { slog.e << "Unable to load " << uri << io::endl; return false; } } if (missingResources) { slog.e << "Some external resources have not been added via addResourceData()" << io::endl; return false; } #else // Read data from the file system and base64 URIs. cgltf_result result = cgltf_load_buffers(&options, (cgltf_data*) gltf, pImpl->mGltfPath.c_str()); if (result != cgltf_result_success) { slog.e << "Unable to load resources." << io::endl; return false; } #endif SYSTRACE_NAME_END(); #ifndef NDEBUG if (cgltf_validate((cgltf_data*) gltf) != cgltf_result_success) { slog.e << "Failed cgltf validation." << io::endl; return false; } #endif // Decompress Draco meshes early on, which allows us to exploit subsequent processing such as // tangent generation. decodeDracoMeshes(asset); // Normalize skinning weights, then "import" each skin into the asset by building a mapping of // skins to their affected entities. if (gltf->skins_count > 0) { if (pImpl->mNormalizeSkinningWeights) { normalizeSkinningWeights(asset); } if (asset->mInstances.empty()) { importSkins(gltf, asset->mNodeMap, asset->mSkins); } else { for (FFilamentInstance* instance : asset->mInstances) { importSkins(gltf, instance->nodeMap, instance->skins); } } } if (pImpl->mRecomputeBoundingBoxes) { updateBoundingBoxes(asset); } Engine& engine = *pImpl->mEngine; // Upload VertexBuffer and IndexBuffer data to the GPU. for (auto slot : asset->mBufferSlots) { const cgltf_accessor* accessor = slot.accessor; if (!accessor->buffer_view) { continue; } auto bufferData = (const uint8_t*) accessor->buffer_view->buffer->data; const uint8_t* data = computeBindingOffset(accessor) + bufferData; const uint32_t size = computeBindingSize(accessor); if (slot.vertexBuffer) { mPool->addPendingUpload(); VertexBuffer::BufferDescriptor bd(data, size, AssetPool::onLoadedResource, mPool); slot.vertexBuffer->setBufferAt(engine, slot.bufferIndex, std::move(bd)); continue; } assert(slot.indexBuffer); if (accessor->component_type == cgltf_component_type_r_8u) { const size_t size16 = size * 2; uint16_t* data16 = (uint16_t*) malloc(size16); convertBytesToShorts(data16, data, size); IndexBuffer::BufferDescriptor bd(data16, size16, FREE_CALLBACK); slot.indexBuffer->setBuffer(engine, std::move(bd)); continue; } mPool->addPendingUpload(); IndexBuffer::BufferDescriptor bd(data, size, AssetPool::onLoadedResource, mPool); slot.indexBuffer->setBuffer(engine, std::move(bd)); } // Apply sparse data modifications to base arrays, then upload the result. applySparseData(asset); // Compute surface orientation quaternions if necessary. This is similar to sparse data in that // we need to generate the contents of a GPU buffer by processing one or more CPU buffer(s). pImpl->computeTangents(asset); // Non-textured renderables are now considered ready, so notify the dependency graph. asset->mDependencyGraph.finalize(); pImpl->mCurrentAsset = asset; // Finally, load image files and create Filament Textures. return pImpl->createTextures(async); } bool ResourceLoader::asyncBeginLoad(FilamentAsset* asset) { return loadResources(upcast(asset), true); } void ResourceLoader::asyncCancelLoad() { pImpl->cancelTextureDecoding(); } float ResourceLoader::asyncGetLoadProgress() const { const float finished = pImpl->mNumDecoderTasksFinished; const float total = pImpl->mNumDecoderTasks; return total == 0 ? 0 : finished / total; } void ResourceLoader::asyncUpdateLoad() { if (!UTILS_HAS_THREADING) { pImpl->decodeSingleTexture(); } pImpl->uploadPendingTextures(); } void ResourceLoader::Impl::decodeSingleTexture() { assert(!UTILS_HAS_THREADING); int w, h, c; // Check if any buffer-based textures haven't been decoded yet. for (auto& pair : mBufferTextureCache) { const uint8_t* sourceData = (const uint8_t*) pair.first; TextureCacheEntry* entry = pair.second.get(); if (entry->texels) { continue; } entry->texels = stbi_load_from_memory(sourceData, entry->bufferSize, &w, &h, &c, 4); return; } // Check if any URI-based textures haven't been decoded yet. for (auto& pair : mUriTextureCache) { auto uri = pair.first; TextureCacheEntry* entry = pair.second.get(); if (entry->texels) { continue; } // First, check the user-supplied resource cache for this URI. auto iter = mUriDataCache.find(uri); if (iter != mUriDataCache.end()) { const uint8_t* sourceData = (const uint8_t*) iter->second.buffer; entry->texels = stbi_load_from_memory(sourceData, iter->second.size, &w, &h, &c, 4); return; } // Otherwise load it from the file system if this platform supports it. #if !USE_FILESYSTEM slog.e << "Unable to load texture: " << uri << io::endl; entry->completed = true; mNumDecoderTasksFinished++; return; #else Path fullpath = Path(mGltfPath).getParent() + uri; entry->texels = stbi_load(fullpath.c_str(), &w, &h, &c, 4); return; #endif } } void ResourceLoader::Impl::uploadPendingTextures() { auto upload = [this](TextureCacheEntry* entry, Engine& engine) { Texture* texture = entry->texture; uint8_t* texels = entry->texels; if (texture && texels && !entry->completed) { Texture::PixelBufferDescriptor pbd(texels, texture->getWidth() * texture->getHeight() * 4, Texture::Format::RGBA, Texture::Type::UBYTE, FREE_CALLBACK); texture->setImage(engine, 0, std::move(pbd)); texture->generateMipmaps(engine); entry->completed = true; mNumDecoderTasksFinished++; mCurrentAsset->mDependencyGraph.markAsReady(texture); } }; for (auto& pair : mBufferTextureCache) upload(pair.second.get(), *mEngine); for (auto& pair : mUriTextureCache) upload(pair.second.get(), *mEngine); } void ResourceLoader::Impl::releasePendingTextures() { auto release = [this](TextureCacheEntry* entry, Engine& engine) { Texture* texture = entry->texture; uint8_t* texels = entry->texels; if (texture && texels && !entry->completed) { // Normally the ownership of these texels is transferred to PixelBufferDescriptor, but // if uploads have been cancelled then we need to free them explicitly. free(texels); } }; for (auto& pair : mBufferTextureCache) release(pair.second.get(), *mEngine); for (auto& pair : mUriTextureCache) release(pair.second.get(), *mEngine); } void ResourceLoader::Impl::addTextureCacheEntry(const TextureSlot& tb) { TextureCacheEntry* entry = nullptr; const cgltf_texture* srcTexture = tb.texture; const cgltf_buffer_view* bv = srcTexture->image->buffer_view; const char* uri = srcTexture->image->uri; const uint32_t totalSize = uint32_t(bv ? bv->size : 0); void** data = bv ? &bv->buffer->data : nullptr; const size_t offset = bv ? bv->offset : 0; // Check if the texture binding uses BufferView data (i.e. it does not have a URI). if (data) { const uint8_t* sourceData = offset + (const uint8_t*) *data; entry = mBufferTextureCache[sourceData] ? mBufferTextureCache[sourceData].get() : nullptr; if (entry) { return; } entry = (mBufferTextureCache[sourceData] = std::make_unique()).get(); entry->srgb = tb.srgb; stbi_info_from_memory(sourceData, totalSize, &entry->width, &entry->height, &entry->numComponents); entry->bufferSize = totalSize; return; } // Check if we already created a Texture object for this URI. entry = mUriTextureCache[uri] ? mUriTextureCache[uri].get() : nullptr; if (entry) { return; } entry = (mUriTextureCache[uri] = std::make_unique()).get(); entry->srgb = tb.srgb; // Check the user-supplied resource cache for this URI, otherwise peek at the file. auto iter = mUriDataCache.find(uri); if (iter != mUriDataCache.end()) { const uint8_t* sourceData = (const uint8_t*) iter->second.buffer; stbi_info_from_memory(sourceData, iter->second.size, &entry->width, &entry->height, &entry->numComponents); return; } #if !USE_FILESYSTEM slog.e << "Unable to load texture: " << uri << io::endl; #else Path fullpath = Path(mGltfPath).getParent() + uri; stbi_info(fullpath.c_str(), &entry->width, &entry->height, &entry->numComponents); #endif } void ResourceLoader::Impl::bindTextureToMaterial(const TextureSlot& tb) { FFilamentAsset* asset = mCurrentAsset; const cgltf_texture* srcTexture = tb.texture; const cgltf_buffer_view* bv = srcTexture->image->buffer_view; const char* uri = srcTexture->image->uri; void** data = bv ? &bv->buffer->data : nullptr; const size_t offset = bv ? bv->offset : 0; // First check if this is a buffer-based texture. if (data) { const uint8_t* sourceData = offset + (const uint8_t*) *data; auto& entry = mBufferTextureCache[sourceData]; if (entry.get() && entry->texture) { asset->bindTexture(tb, entry->texture); } return; } // Next check if this is a URI-based texture. auto& entry = mUriTextureCache[uri]; if (entry.get() && entry->texture) { asset->bindTexture(tb, entry->texture); } } void ResourceLoader::Impl::cancelTextureDecoding() { JobSystem* js = &mEngine->getJobSystem(); if (mDecoderRootJob) { js->waitAndRelease(mDecoderRootJob); mDecoderRootJob = nullptr; } releasePendingTextures(); mBufferTextureCache.clear(); mUriTextureCache.clear(); mCurrentAsset = nullptr; mNumDecoderTasksFinished = 0; mNumDecoderTasks = 0; } bool ResourceLoader::Impl::createTextures(bool async) { // If any decoding jobs are still underway, wait for them to finish. JobSystem* js = &mEngine->getJobSystem(); if (mDecoderRootJob) { js->waitAndRelease(mDecoderRootJob); mDecoderRootJob = nullptr; } mBufferTextureCache.clear(); mUriTextureCache.clear(); // First, determine texture dimensions and create texture cache entries. FFilamentAsset* asset = mCurrentAsset; for (auto slot : asset->mTextureSlots) { addTextureCacheEntry(slot); } // Tally up the total number of textures that need to be decoded. Zero textures is a special // case that needs to report 100% progress right away, so we set NumDecoderTasks and Finished // both to 1. If they were both 0, this would indicate that loading has not started. mNumDecoderTasks = mBufferTextureCache.size() + mUriTextureCache.size(); if (mNumDecoderTasks == 0) { mNumDecoderTasks = 1; mNumDecoderTasksFinished = 1; } else { mNumDecoderTasksFinished = 0; } // Next create blank Filament textures. auto createTexture = [=](TextureCacheEntry* entry) { entry->texture = Texture::Builder() .width(entry->width) .height(entry->height) .levels(0xff) .format(entry->srgb ? Texture::InternalFormat::SRGB8_A8 : Texture::InternalFormat::RGBA8) .build(*mEngine); asset->takeOwnership(entry->texture); }; for (auto& pair : mBufferTextureCache) createTexture(pair.second.get()); for (auto& pair : mUriTextureCache) createTexture(pair.second.get()); // Bind the textures to material instances. for (auto slot : asset->mTextureSlots) { bindTextureToMaterial(slot); } // Before creating jobs for PNG / JPEG decoding, we might need to return early. On single // threaded systems, it is usually fine to create jobs because the job system will simply // execute serially. However if the client requests async behavior, then we need to wait // until subsequent calls to asyncUpdateLoad(). if (!UTILS_HAS_THREADING && async) { return true; } JobSystem::Job* parent = js->createJob(); // Kick off jobs that decode texels from buffer pointers. for (auto& pair : mBufferTextureCache) { const uint8_t* sourceData = (const uint8_t*) pair.first; TextureCacheEntry* entry = pair.second.get(); JobSystem::Job* decode = jobs::createJob(*js, parent, [=] { int width, height, comp; entry->texels = stbi_load_from_memory(sourceData, entry->bufferSize, &width, &height, &comp, 4); }); js->run(decode); } // Kick off jobs that decode texels from URI strings. for (auto& pair : mUriTextureCache) { auto uri = pair.first; TextureCacheEntry* entry = pair.second.get(); // First, check the user-supplied resource cache for this URI. auto iter = mUriDataCache.find(uri); if (iter != mUriDataCache.end()) { const uint8_t* sourceData = (const uint8_t*) iter->second.buffer; JobSystem::Job* decode = jobs::createJob(*js, parent, [=] { int width, height, comp; entry->texels = stbi_load_from_memory(sourceData, iter->second.size, &width, &height, &comp, 4); }); js->run(decode); continue; } // Otherwise load it from the file system if this platform supports it. #if !USE_FILESYSTEM slog.e << "Unable to load texture: " << uri << io::endl; return false; #else Path fullpath = Path(mGltfPath).getParent() + uri; JobSystem::Job* decode = jobs::createJob(*js, parent, [=] { int width, height, comp; entry->texels = stbi_load(fullpath.c_str(), &width, &height, &comp, 4); }); js->run(decode); #endif } if (async) { mDecoderRootJob = js->runAndRetain(parent); return true; } // Wait for decoding to finish. js->runAndWait(parent); // Finally, upload texels to the GPU and generate mipmaps. mCurrentAsset = asset; uploadPendingTextures(); return true; } void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) { SYSTRACE_CALL(); const cgltf_accessor* kGenerateTangents = &asset->mGenerateTangents; const cgltf_accessor* kGenerateNormals = &asset->mGenerateNormals; struct JobParams { // Consumed by the job: const cgltf_primitive* prim; VertexBuffer* const vb; const uint8_t slot; const int morphTargetIndex; // Produced by the job: cgltf_size vertexCount; short4* results; }; constexpr int kMorphTargetUnused = -1; auto computeQuats = [&](JobParams* params) { const cgltf_primitive& prim = *params->prim; const uint8_t slot = params->slot; const int morphTargetIndex = params->morphTargetIndex; // Declare vectors of normals and tangents, which we'll extract & convert from the source. std::vector fp32Normals; std::vector fp32Tangents; std::vector fp32Positions; std::vector fp32TexCoords; std::vector ui32Triangles; cgltf_size vertexCount = 0; // Build a mapping from cgltf_attribute_type to cgltf_accessor*. const int NUM_ATTRIBUTES = 8; const cgltf_accessor* accessors[NUM_ATTRIBUTES] = {}; // Collect accessors for normals, tangents, etc. if (morphTargetIndex == kMorphTargetUnused) { for (cgltf_size aindex = 0; aindex < prim.attributes_count; aindex++) { const cgltf_attribute& attr = prim.attributes[aindex]; if (attr.index == 0) { accessors[attr.type] = attr.data; vertexCount = attr.data->count; } } } else { const cgltf_morph_target& morphTarget = prim.targets[morphTargetIndex]; for (cgltf_size aindex = 0; aindex < morphTarget.attributes_count; aindex++) { const cgltf_attribute& attr = prim.attributes[aindex]; if (attr.index == 0) { accessors[attr.type] = attr.data; vertexCount = attr.data->count; } } } params->vertexCount = vertexCount; // At a minimum we need normals to generate tangents. auto normalsInfo = accessors[cgltf_attribute_type_normal]; if (vertexCount == 0) { return; } geometry::SurfaceOrientation::Builder sob; sob.vertexCount(vertexCount); // Convert normals into packed floats. if (normalsInfo) { assert(normalsInfo->count == vertexCount); assert(normalsInfo->type == cgltf_type_vec3); fp32Normals.resize(vertexCount); cgltf_accessor_unpack_floats(normalsInfo, &fp32Normals[0].x, vertexCount * 3); sob.normals(fp32Normals.data()); } // Convert tangents into packed floats. auto tangentsInfo = accessors[cgltf_attribute_type_tangent]; if (tangentsInfo) { if (tangentsInfo->count != vertexCount || tangentsInfo->type != cgltf_type_vec4) { slog.e << "Bad tangent count or type." << io::endl; return; } fp32Tangents.resize(vertexCount); cgltf_accessor_unpack_floats(tangentsInfo, &fp32Tangents[0].x, vertexCount * 4); sob.tangents(fp32Tangents.data()); } auto positionsInfo = accessors[cgltf_attribute_type_position]; if (positionsInfo) { if (positionsInfo->count != vertexCount || positionsInfo->type != cgltf_type_vec3) { slog.e << "Bad position count or type." << io::endl; return; } fp32Positions.resize(vertexCount); cgltf_accessor_unpack_floats(positionsInfo, &fp32Positions[0].x, vertexCount * 3); sob.positions(fp32Positions.data()); } if (prim.indices) { size_t triangleCount = prim.indices->count / 3; ui32Triangles.resize(triangleCount); cgltf_size j = 0; for (auto& triangle : ui32Triangles) { triangle.x = cgltf_accessor_read_index(prim.indices, j++); triangle.y = cgltf_accessor_read_index(prim.indices, j++); triangle.z = cgltf_accessor_read_index(prim.indices, j++); } } else { size_t triangleCount = vertexCount / 3; ui32Triangles.resize(triangleCount); cgltf_size j = 0; for (auto& triangle : ui32Triangles) { triangle.x = j++; triangle.y = j++; triangle.z = j++; } } sob.triangleCount(ui32Triangles.size()); sob.triangles(ui32Triangles.data()); auto texcoordsInfo = accessors[cgltf_attribute_type_texcoord]; if (texcoordsInfo) { if (texcoordsInfo->count != vertexCount || texcoordsInfo->type != cgltf_type_vec2) { slog.e << "Bad texture coordinate count or type." << io::endl; return; } fp32TexCoords.resize(vertexCount); cgltf_accessor_unpack_floats(texcoordsInfo, &fp32TexCoords[0].x, vertexCount * 2); sob.uvs(fp32TexCoords.data()); } // Compute surface orientation quaternions. params->results = (short4*) malloc(sizeof(short4) * vertexCount); geometry::SurfaceOrientation* helper = sob.build(); helper->getQuats(params->results, vertexCount); delete helper; }; // Collect all TANGENT vertex attribute slots that need to be populated. tsl::robin_map baseTangents; tsl::robin_map morphTangents[4]; for (auto slot : asset->mBufferSlots) { if (slot.accessor != kGenerateTangents && slot.accessor != kGenerateNormals) { continue; } if (slot.morphTarget) { morphTangents[slot.morphTarget - 1][slot.vertexBuffer] = slot.bufferIndex; continue; } baseTangents[slot.vertexBuffer] = slot.bufferIndex; } // Create a job description for each primitive. std::vector jobParams; for (auto pair : asset->mPrimitives) { VertexBuffer* vb = pair.second; auto iter = baseTangents.find(vb); if (iter != baseTangents.end()) { jobParams.emplace_back(JobParams { pair.first, vb, iter->second, kMorphTargetUnused }); } for (int morphTarget = 0; morphTarget < 4; morphTarget++) { const auto& tangents = morphTangents[morphTarget]; auto iter = tangents.find(vb); if (iter != tangents.end()) { jobParams.emplace_back(JobParams { pair.first, vb, iter->second, morphTarget }); } } } // Kick off jobs for computing tangent frames. JobSystem* js = &mEngine->getJobSystem(); JobSystem::Job* parent = js->createJob(); for (JobParams& params : jobParams) { JobParams* pptr = ¶ms; js->run(jobs::createJob(*js, parent, [pptr, computeQuats] { computeQuats(pptr); })); } js->runAndWait(parent); // Finally, upload quaternions to the GPU from the main thread. for (JobParams& params : jobParams) { VertexBuffer::BufferDescriptor bd(params.results, params.vertexCount * sizeof(short4), FREE_CALLBACK); params.vb->setBufferAt(*mEngine, params.slot, std::move(bd)); } } ResourceLoader::Impl::~Impl() { if (mDecoderRootJob) { mEngine->getJobSystem().waitAndRelease(mDecoderRootJob); } } void ResourceLoader::applySparseData(FFilamentAsset* asset) const { for (auto slot : asset->mBufferSlots) { const cgltf_accessor* accessor = slot.accessor; if (!accessor->is_sparse) { continue; } cgltf_size numFloats = accessor->count * cgltf_num_components(accessor->type); cgltf_size numBytes = sizeof(float) * numFloats; float* generated = (float*) malloc(numBytes); cgltf_accessor_unpack_floats(accessor, generated, numFloats); VertexBuffer::BufferDescriptor bd(generated, numBytes, FREE_CALLBACK); slot.vertexBuffer->setBufferAt(*pImpl->mEngine, slot.bufferIndex, std::move(bd)); } } void ResourceLoader::normalizeSkinningWeights(FFilamentAsset* asset) const { auto normalize = [](cgltf_accessor* data) { if (data->type != cgltf_type_vec4 || data->component_type != cgltf_component_type_r_32f) { slog.w << "Cannot normalize weights, unsupported attribute type." << io::endl; return; } uint8_t* bytes = (uint8_t*) data->buffer_view->buffer->data; float4* floats = (float4*) (bytes + data->offset + data->buffer_view->offset); for (cgltf_size i = 0; i < data->count; ++i) { float4 weights = floats[i]; float sum = weights.x + weights.y + weights.z + weights.w; floats[i] = weights / sum; } }; const cgltf_data* gltf = asset->mSourceAsset; cgltf_size mcount = gltf->meshes_count; for (cgltf_size mindex = 0; mindex < mcount; ++mindex) { const cgltf_mesh& mesh = gltf->meshes[mindex]; cgltf_size pcount = mesh.primitives_count; for (cgltf_size pindex = 0; pindex < pcount; ++pindex) { const cgltf_primitive& prim = mesh.primitives[pindex]; cgltf_size acount = prim.attributes_count; for (cgltf_size aindex = 0; aindex < acount; ++aindex) { const auto& attr = prim.attributes[aindex]; if (attr.type == cgltf_attribute_type_weights) { normalize(attr.data); } } } } } void ResourceLoader::updateBoundingBoxes(FFilamentAsset* asset) const { SYSTRACE_CALL(); auto& rm = pImpl->mEngine->getRenderableManager(); auto& tm = pImpl->mEngine->getTransformManager(); NodeMap& nodeMap = asset->mInstances.empty() ? asset->mNodeMap : asset->mInstances[0]->nodeMap; // The purpose of the root node is to give the client a place for custom transforms. // Since it is not part of the source model, it should be ignored when computing the // bounding box. TransformManager::Instance root = tm.getInstance(asset->getRoot()); std::vector modelRoots(tm.getChildCount(root)); tm.getChildren(root, modelRoots.data(), modelRoots.size()); for (auto e : modelRoots) { tm.setParent(tm.getInstance(e), 0); } auto computeBoundingBox = [](const cgltf_primitive* prim, Aabb* result) { Aabb aabb; for (cgltf_size slot = 0; slot < prim->attributes_count; slot++) { const cgltf_attribute& attr = prim->attributes[slot]; const cgltf_accessor* accessor = attr.data; const size_t dim = cgltf_num_components(accessor->type); if (attr.type == cgltf_attribute_type_position && dim >= 3) { std::vector unpacked(accessor->count * dim); cgltf_accessor_unpack_floats(accessor, unpacked.data(), unpacked.size()); for (cgltf_size i = 0, j = 0, n = accessor->count; i < n; ++i, j += dim) { float3 pt(unpacked[j + 0], unpacked[j + 1], unpacked[j + 2]); aabb.min = min(aabb.min, pt); aabb.max = max(aabb.max, pt); } break; } } *result = aabb; }; // Collect all mesh primitives that we wish to find bounds for. std::vector prims; for (auto iter : nodeMap) { const cgltf_mesh* mesh = iter.first->mesh; if (mesh) { for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) { prims.push_back(&mesh->primitives[index]); } } } // Kick off a bounding box job for every primitive. std::vector bounds(prims.size()); JobSystem* js = &pImpl->mEngine->getJobSystem(); JobSystem::Job* parent = js->createJob(); for (size_t i = 0; i < prims.size(); ++i) { cgltf_primitive const* prim = prims[i]; Aabb* result = &bounds[i]; js->run(jobs::createJob(*js, parent, [prim, result, computeBoundingBox] { computeBoundingBox(prim, result); })); } js->runAndWait(parent); // Compute the asset-level bounding box. size_t primIndex = 0; Aabb assetBounds; for (auto iter : nodeMap) { const cgltf_mesh* mesh = iter.first->mesh; if (mesh) { // Find the object-space bounds for the renderable by unioning the bounds of each prim. Aabb aabb; for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) { Aabb primBounds = bounds[primIndex++]; aabb.min = min(aabb.min, primBounds.min); aabb.max = max(aabb.max, primBounds.max); } auto renderable = rm.getInstance(iter.second); rm.setAxisAlignedBoundingBox(renderable, Box().set(aabb.min, aabb.max)); // Transform this bounding box, then update the asset-level bounding box. auto transformable = tm.getInstance(iter.second); const mat4f worldTransform = tm.getWorldTransform(transformable); const Aabb transformed = aabb.transform(worldTransform); assetBounds.min = min(assetBounds.min, transformed.min); assetBounds.max = max(assetBounds.max, transformed.max); } } for (auto e : modelRoots) { tm.setParent(tm.getInstance(e), root); } asset->mBoundingBox = assetBounds; } } // namespace gltfio