gltfio: Use JobSystem to compute tangents.

This commit is contained in:
Philip Rideout
2020-05-29 11:43:59 -07:00
parent b1563dabe9
commit 96c770ad9e
2 changed files with 87 additions and 58 deletions

View File

@@ -30,6 +30,7 @@
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
#include <cgltf.h>
@@ -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<float3> fp32Normals;
std::vector<float4> fp32Tangents;
std::vector<float3> fp32Positions;
std::vector<float2> fp32TexCoords;
std::vector<uint3> 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<float3> fp32Normals;
std::vector<float4> fp32Tangents;
std::vector<float3> fp32Positions;
std::vector<float2> fp32TexCoords;
std::vector<uint3> 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> 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 = &params;
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();