From 96c770ad9e2045b63f5985dcffddea87d14837a5 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Fri, 29 May 2020 11:43:59 -0700 Subject: [PATCH] gltfio: Use JobSystem to compute tangents. --- libs/gltfio/include/gltfio/ResourceLoader.h | 1 - libs/gltfio/src/ResourceLoader.cpp | 144 ++++++++++++-------- 2 files changed, 87 insertions(+), 58 deletions(-) diff --git a/libs/gltfio/include/gltfio/ResourceLoader.h b/libs/gltfio/include/gltfio/ResourceLoader.h index 4cf126c263..e200f86625 100644 --- a/libs/gltfio/include/gltfio/ResourceLoader.h +++ b/libs/gltfio/include/gltfio/ResourceLoader.h @@ -135,7 +135,6 @@ public: private: bool loadResources(details::FFilamentAsset* asset, bool async); void applySparseData(details::FFilamentAsset* asset) const; - void computeTangents(details::FFilamentAsset* asset) const; void normalizeSkinningWeights(details::FFilamentAsset* asset) const; void updateBoundingBoxes(details::FFilamentAsset* asset) const; details::AssetPool* mPool; diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index ba7c3fd56d..b560499048 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -30,6 +30,7 @@ #include #include +#include #include @@ -51,6 +52,7 @@ using namespace filament; using namespace filament::math; using namespace utils; +using namespace gltfio::details; static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { free(mem); }; @@ -98,12 +100,13 @@ struct ResourceLoader::Impl { UriTextureCache mUriTextureCache; int mNumDecoderTasks; int mNumDecoderTasksFinished; - utils::JobSystem::Job* mDecoderRootJob = nullptr; - details::FFilamentAsset* mCurrentAsset; + JobSystem::Job* mDecoderRootJob = nullptr; + FFilamentAsset* mCurrentAsset; + void computeTangents(FFilamentAsset* asset); bool createTextures(bool async); - void addTextureCacheEntry(const details::TextureSlot& tb); - void bindTextureToMaterial(const details::TextureSlot& tb); + void addTextureCacheEntry(const TextureSlot& tb); + void bindTextureToMaterial(const TextureSlot& tb); void decodeSingleTexture(); void uploadPendingTextures(); ~Impl(); @@ -432,7 +435,7 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { // 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). - computeTangents(asset); + pImpl->computeTangents(asset); // Non-textured renderables are now considered ready, so notify the dependency graph. asset->mDependencyGraph.finalize(); @@ -497,7 +500,7 @@ void ResourceLoader::Impl::decodeSingleTexture() { mNumDecoderTasksFinished++; return; #else - utils::Path fullpath = utils::Path(mGltfPath).getParent() + uri; + Path fullpath = Path(mGltfPath).getParent() + uri; entry->texels = stbi_load(fullpath.c_str(), &w, &h, &c, 4); return; #endif @@ -568,13 +571,13 @@ void ResourceLoader::Impl::addTextureCacheEntry(const TextureSlot& tb) { #if !USE_FILESYSTEM slog.e << "Unable to load texture: " << uri << io::endl; #else - utils::Path fullpath = utils::Path(mGltfPath).getParent() + uri; + 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) { - details::FFilamentAsset* asset = mCurrentAsset; + FFilamentAsset* asset = mCurrentAsset; const cgltf_texture* srcTexture = tb.texture; const cgltf_buffer_view* bv = srcTexture->image->buffer_view; @@ -601,7 +604,7 @@ void ResourceLoader::Impl::bindTextureToMaterial(const TextureSlot& tb) { bool ResourceLoader::Impl::createTextures(bool async) { // If any decoding jobs are still underway, wait for them to finish. - utils::JobSystem* js = &mEngine->getJobSystem(); + JobSystem* js = &mEngine->getJobSystem(); if (mDecoderRootJob) { js->waitAndRelease(mDecoderRootJob); mDecoderRootJob = nullptr; @@ -611,7 +614,7 @@ bool ResourceLoader::Impl::createTextures(bool async) { mUriTextureCache.clear(); // First, determine texture dimensions and create texture cache entries. - details::FFilamentAsset* asset = mCurrentAsset; + FFilamentAsset* asset = mCurrentAsset; for (auto slot : asset->mTextureSlots) { addTextureCacheEntry(slot); } @@ -653,13 +656,13 @@ bool ResourceLoader::Impl::createTextures(bool async) { return true; } - utils::JobSystem::Job* parent = js->createJob(); + 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(); - utils::JobSystem::Job* decode = utils::jobs::createJob(*js, parent, [=] { + JobSystem::Job* decode = jobs::createJob(*js, parent, [=] { int width, height, comp; entry->texels = stbi_load_from_memory(sourceData, entry->bufferSize, &width, &height, &comp, 4); @@ -676,7 +679,7 @@ bool ResourceLoader::Impl::createTextures(bool async) { auto iter = mUriDataCache.find(uri); if (iter != mUriDataCache.end()) { const uint8_t* sourceData = (const uint8_t*) iter->second.buffer; - utils::JobSystem::Job* decode = utils::jobs::createJob(*js, parent, [=] { + 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); @@ -690,8 +693,8 @@ bool ResourceLoader::Impl::createTextures(bool async) { slog.e << "Unable to load texture: " << uri << io::endl; return false; #else - utils::Path fullpath = utils::Path(mGltfPath).getParent() + uri; - utils::JobSystem::Job* decode = utils::jobs::createJob(*js, parent, [=] { + 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); }); @@ -714,42 +717,36 @@ bool ResourceLoader::Impl::createTextures(bool async) { return true; } -ResourceLoader::Impl::~Impl() { - if (mDecoderRootJob) { - mEngine->getJobSystem().waitAndRelease(mDecoderRootJob); - } -} +void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) { + SYSTRACE_CALL(); -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::computeTangents(FFilamentAsset* asset) const { const cgltf_accessor* kGenerateTangents = &asset->mGenerateTangents; const cgltf_accessor* kGenerateNormals = &asset->mGenerateNormals; - // 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; + 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 = [&](const cgltf_primitive& prim, VertexBuffer* vb, uint8_t slot, - int morphTargetIndex) { + 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; @@ -776,6 +773,7 @@ void ResourceLoader::computeTangents(FFilamentAsset* asset) const { } } } + params->vertexCount = vertexCount; // At a minimum we need normals to generate tangents. auto normalsInfo = accessors[cgltf_attribute_type_normal]; @@ -783,8 +781,6 @@ void ResourceLoader::computeTangents(FFilamentAsset* asset) const { return; } - short4* quats = (short4*) malloc(sizeof(short4) * vertexCount); - geometry::SurfaceOrientation::Builder sob; sob.vertexCount(vertexCount); @@ -855,13 +851,10 @@ void ResourceLoader::computeTangents(FFilamentAsset* asset) const { } // Compute surface orientation quaternions. + params->results = (short4*) malloc(sizeof(short4) * vertexCount); geometry::SurfaceOrientation* helper = sob.build(); - helper->getQuats(quats, vertexCount); + helper->getQuats(params->results, vertexCount); delete helper; - - // Upload quaternions to the GPU. - VertexBuffer::BufferDescriptor bd(quats, vertexCount * sizeof(short4), FREE_CALLBACK); - vb->setBufferAt(*pImpl->mEngine, slot, std::move(bd)); }; // Collect all TANGENT vertex attribute slots that need to be populated. @@ -878,25 +871,62 @@ void ResourceLoader::computeTangents(FFilamentAsset* asset) const { baseTangents[slot.vertexBuffer] = slot.bufferIndex; } - // Go through all cgltf primitives and populate their tangents if requested. + // Create a job description for each primitive. + std::vector jobParams; for (auto pair : asset->mPrimitives) { - const cgltf_primitive& prim = *pair.first; VertexBuffer* vb = pair.second; auto iter = baseTangents.find(vb); if (iter != baseTangents.end()) { - computeQuats(prim, vb, iter->second, kMorphTargetUnused); + 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()) { - computeQuats(prim, vb, iter->second, morphTarget); + 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)); + } } -void ResourceLoader::normalizeSkinningWeights(details::FFilamentAsset* asset) const { +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; @@ -928,7 +958,7 @@ void ResourceLoader::normalizeSkinningWeights(details::FFilamentAsset* asset) co } } -void ResourceLoader::updateBoundingBoxes(details::FFilamentAsset* asset) const { +void ResourceLoader::updateBoundingBoxes(FFilamentAsset* asset) const { auto& rm = pImpl->mEngine->getRenderableManager(); auto& tm = pImpl->mEngine->getTransformManager();