gltfio: Use BufferObject API, simplify MorphHelper. (#3737)
* gltfio: Use BufferObject API, simplify MorphHelper. * Repair Android build.
This commit is contained in:
@@ -2,6 +2,7 @@ LIBFILAMENT {
|
||||
global:
|
||||
Java_com_google_android_filament_*;
|
||||
JNI*;
|
||||
*filament*BufferObject*;
|
||||
*filament*Camera*;
|
||||
*filament*Color*;
|
||||
*filament*Exposure*;
|
||||
|
||||
@@ -78,7 +78,6 @@ struct AnimatorImpl {
|
||||
vector<float> weights;
|
||||
MorphHelper* morpher;
|
||||
void addChannels(const NodeMap& nodeMap, const cgltf_animation& srcAnim, Animation& dst);
|
||||
void prepareMorphing(const Channel& channel);
|
||||
void applyAnimation(const Channel& channel, float t, size_t prevIndex, size_t nextIndex);
|
||||
};
|
||||
|
||||
@@ -371,29 +370,9 @@ void AnimatorImpl::addChannels(const NodeMap& nodeMap, const cgltf_animation& sr
|
||||
dstChannel.targetEntity = targetEntity;
|
||||
setTransformType(srcChannel, dstChannel);
|
||||
dst.channels.push_back(dstChannel);
|
||||
if (dstChannel.transformType == Channel::WEIGHTS) {
|
||||
prepareMorphing(dstChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function primes the cache in MorphHelper, which allows users to free their source data
|
||||
// after obtaining the Animator interface. We do this by sampling exactly half-way between key
|
||||
// frames. In practice this is sufficient, and MorphHelper degrades gracefully for corner cases.
|
||||
void AnimatorImpl::prepareMorphing(const Channel& channel) {
|
||||
const Sampler* sampler = channel.sourceData;
|
||||
const TimeValues& times = sampler->times;
|
||||
auto iter = times.begin();
|
||||
morpher->disableWrites(true);
|
||||
for (size_t timeIndex = 0; timeIndex < times.size(); ++timeIndex, ++iter) {
|
||||
auto next = iter;
|
||||
++next;
|
||||
next = next == times.end() ? iter : next;
|
||||
applyAnimation(channel, 0.5, iter->second, next->second);
|
||||
}
|
||||
morpher->disableWrites(false);
|
||||
}
|
||||
|
||||
void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIndex,
|
||||
size_t nextIndex) {
|
||||
const Sampler* sampler = channel.sourceData;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "GltfEnums.h"
|
||||
|
||||
#include <filament/Box.h>
|
||||
#include <filament/BufferObject.h>
|
||||
#include <filament/Camera.h>
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/IndexBuffer.h>
|
||||
@@ -560,6 +561,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
mResult->mIndexBuffers.push_back(indices);
|
||||
|
||||
VertexBuffer::Builder vbb;
|
||||
vbb.enableBufferObjects();
|
||||
|
||||
bool hasUv0 = false, hasUv1 = false, hasVertexColor = false, hasNormals = false;
|
||||
uint32_t vertexCount = 0;
|
||||
@@ -777,10 +779,13 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
|
||||
if (needsDummyData) {
|
||||
uint32_t size = sizeof(ubyte4) * vertexCount;
|
||||
BufferObject* bufferObject = BufferObject::Builder().size(size).build(*mEngine);
|
||||
mResult->mBufferObjects.push_back(bufferObject);
|
||||
uint32_t* dummyData = (uint32_t*) malloc(size);
|
||||
memset(dummyData, 0xff, size);
|
||||
VertexBuffer::BufferDescriptor bd(dummyData, size, FREE_CALLBACK);
|
||||
vertices->setBufferAt(*mEngine, slot, std::move(bd));
|
||||
bufferObject->setBuffer(*mEngine, std::move(bd));
|
||||
vertices->setBufferObjectAt(*mEngine, slot, bufferObject);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -229,6 +229,7 @@ struct FFilamentAsset : public FilamentAsset {
|
||||
std::vector<utils::Entity> mCameraEntities;
|
||||
std::vector<filament::MaterialInstance*> mMaterialInstances;
|
||||
std::vector<filament::VertexBuffer*> mVertexBuffers;
|
||||
std::vector<filament::BufferObject*> mBufferObjects;
|
||||
std::vector<filament::IndexBuffer*> mIndexBuffers;
|
||||
std::vector<filament::Texture*> mTextures;
|
||||
filament::Aabb mBoundingBox;
|
||||
|
||||
@@ -61,6 +61,9 @@ FFilamentAsset::~FFilamentAsset() {
|
||||
for (auto vb : mVertexBuffers) {
|
||||
mEngine->destroy(vb);
|
||||
}
|
||||
for (auto bo : mBufferObjects) {
|
||||
mEngine->destroy(bo);
|
||||
}
|
||||
for (auto ib : mIndexBuffers) {
|
||||
mEngine->destroy(ib);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "MorphHelper.h"
|
||||
|
||||
#include <filament/BufferObject.h>
|
||||
#include <filament/RenderableManager.h>
|
||||
#include <filament/VertexBuffer.h>
|
||||
|
||||
@@ -33,6 +34,8 @@ namespace gltfio {
|
||||
uint32_t computeBindingSize(const cgltf_accessor* accessor);
|
||||
uint32_t computeBindingOffset(const cgltf_accessor* accessor);
|
||||
|
||||
static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { free(mem); };
|
||||
|
||||
// Returns true if a is a subset of b.
|
||||
static bool isSubsetOf(ubyte4 a, ubyte4 b) {
|
||||
if (a.x != kUnused && a.x != b.x && a.x != b.y && a.x != b.z && a.x != b.w) return false;
|
||||
@@ -42,40 +45,52 @@ static bool isSubsetOf(ubyte4 a, ubyte4 b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static int indexOf(int a, ubyte4 b) {
|
||||
if (a == b.x) return 0;
|
||||
if (a == b.y) return 1;
|
||||
if (a == b.z) return 2;
|
||||
if (a == b.w) return 3;
|
||||
return -1;
|
||||
}
|
||||
|
||||
MorphHelper::MorphHelper(FFilamentAsset* asset, FFilamentInstance* inst) : mAsset(asset),
|
||||
mInstance(inst) {
|
||||
NodeMap& sourceNodes = asset->isInstanced() ? asset->mInstances[0]->nodeMap : asset->mNodeMap;
|
||||
for (auto pair : sourceNodes) {
|
||||
mMorphTable[pair.second].sourceNode = pair.first;
|
||||
cgltf_node const* node = pair.first;
|
||||
cgltf_mesh const* mesh = node->mesh;
|
||||
if (mesh) {
|
||||
cgltf_primitive const* prims = mesh->primitives;
|
||||
for (cgltf_size pi = 0, count = mesh->primitives_count; pi < count; ++pi) {
|
||||
addPrimitive(mesh, pi, &mMorphTable[pair.second]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MorphHelper::~MorphHelper() {
|
||||
auto engine = mAsset->mEngine;
|
||||
for (auto& entry : mMorphTable) {
|
||||
for (auto& perm : entry.second.permutations) {
|
||||
if (perm.owner) {
|
||||
for (auto& prim : perm.primitives) {
|
||||
engine->destroy(prim.vertices);
|
||||
}
|
||||
for (auto& prim : entry.second.primitives) {
|
||||
for (auto& target : prim.targets) {
|
||||
engine->destroy(target.bufferObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MorphHelper::applyWeights(Entity entity, float const* weights, size_t count) noexcept {
|
||||
auto renderableManager = &mAsset->mEngine->getRenderableManager();
|
||||
auto& engine = *mAsset->mEngine;
|
||||
auto renderableManager = &engine.getRenderableManager();
|
||||
auto renderable = renderableManager->getInstance(entity);
|
||||
|
||||
// If there are 4 or fewer targets, we can simply re-use the original VertexBuffer.
|
||||
if (count <= 4) {
|
||||
if (!mWritesDisabled) {
|
||||
float4 vec{};
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
vec[i] = weights[i];
|
||||
}
|
||||
renderableManager->setMorphWeights(renderable, vec);
|
||||
float4 vec{};
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
vec[i] = weights[i];
|
||||
}
|
||||
renderableManager->setMorphWeights(renderable, vec);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,216 +117,38 @@ void MorphHelper::applyWeights(Entity entity, float const* weights, size_t count
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we already encountered this set.
|
||||
Permutation* permutation = nullptr;
|
||||
auto iter = mMorphTable.find(entity);
|
||||
if (iter == mMorphTable.end()) return;
|
||||
for (auto& perm : iter.value().permutations) {
|
||||
if (isSubsetOf(primaryIndices, perm.primaryIndices)) {
|
||||
permutation = &perm;
|
||||
break;
|
||||
// Swap out the buffer objects for the primary indices.
|
||||
for (const auto& prim : mMorphTable[entity].primitives) {
|
||||
for (const auto& target : prim.targets) {
|
||||
const int index = indexOf(target.morphTargetIndex, primaryIndices);
|
||||
if (index > -1) {
|
||||
VertexBuffer* vb = prim.vertexBuffer;
|
||||
vb->setBufferObjectAt(engine, prim.baseSlot + index, target.bufferObject);
|
||||
// Do not break out early because there could be more than one target entry for this
|
||||
// particular target index (e.g. positions + tangents).
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't found an existing set, then create a new partition. This is a potentially
|
||||
// slow operation but it should only occur during the priming phase.
|
||||
if (permutation == nullptr) {
|
||||
if (!mAsset->getSourceAsset()) return;
|
||||
addPermutation(entity, primaryIndices);
|
||||
permutation = &mMorphTable[entity].permutations.back();
|
||||
}
|
||||
|
||||
// Leave early if we're priming the cache.
|
||||
if (mWritesDisabled) return;
|
||||
|
||||
// Swap out the vertex buffer on all affected renderables. Often this will be a no-op,
|
||||
// but this is a fairly efficient operation in Filament.
|
||||
for (size_t primIndex = 0; primIndex < permutation->primitives.size(); ++primIndex) {
|
||||
const GltfPrimitive& prim = permutation->primitives[primIndex];
|
||||
renderableManager->setGeometryAt(renderable, primIndex, prim.type, prim.vertices,
|
||||
prim.indices, 0, prim.indices->getIndexCount());
|
||||
}
|
||||
|
||||
// Finally, set the 4-tuple uniform for the weight values by derefing the primary indices.
|
||||
// Note that we first create a "safe set" by replacing the unused sentinel with zero.
|
||||
ubyte4 safe = permutation->primaryIndices;
|
||||
float4 highest;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (safe[i] == kUnused) {
|
||||
safe[i] = 0;
|
||||
}
|
||||
highest[i] = (primaryIndices[i] == kUnused) ? 0 : weights[primaryIndices[i]];
|
||||
}
|
||||
float4 highest(weights[safe[0]], weights[safe[1]], weights[safe[2]], weights[safe[3]]);
|
||||
renderableManager->setMorphWeights(renderable, highest);
|
||||
}
|
||||
|
||||
void MorphHelper::addPermutation(Entity entity, ubyte4 primaryIndices) {
|
||||
Permutation result = { primaryIndices };
|
||||
const cgltf_node* node = mMorphTable[entity].sourceNode;
|
||||
const cgltf_mesh* mesh = node->mesh;
|
||||
const cgltf_primitive* prims = mesh->primitives;
|
||||
const cgltf_size prim_count = mesh->primitives_count;
|
||||
void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, TableEntry* entry) {
|
||||
auto& engine = *mAsset->mEngine;
|
||||
const cgltf_primitive& prim = mesh->primitives[primitiveIndex];
|
||||
VertexBuffer* vertexBuffer = mAsset->mMeshCache.at(mesh)[primitiveIndex].vertices;
|
||||
|
||||
// If the original VertexBuffer can be used, just use that instead of creating a new one.
|
||||
// In such a case, the MorphHelper is not the owner of the vertex buffer.
|
||||
const ubyte4 kOriginalPrimaryIndices(0, 1, 2, 3);
|
||||
result.owner = !isSubsetOf(primaryIndices, kOriginalPrimaryIndices);
|
||||
if (!result.owner) {
|
||||
result.primaryIndices = kOriginalPrimaryIndices;
|
||||
}
|
||||
entry->primitives.push_back({ vertexBuffer, determineBaseSlot(prim) });
|
||||
std::vector<GltfTarget>& targets = entry->primitives.back().targets;
|
||||
|
||||
for (cgltf_size pi = 0; pi < prim_count; ++pi) {
|
||||
const cgltf_primitive& prim = prims[pi];
|
||||
RenderableManager::PrimitiveType prim_type;
|
||||
getPrimitiveType(prim.type, &prim_type);
|
||||
const auto& gltfioPrim = mAsset->mMeshCache.at(mesh)[pi];
|
||||
VertexBuffer* const vertices = result.owner ?
|
||||
createVertexBuffer(prim, gltfioPrim.uvmap, primaryIndices) :
|
||||
gltfioPrim.vertices;
|
||||
result.primitives.emplace_back(GltfPrimitive {
|
||||
.vertices = vertices,
|
||||
.indices = gltfioPrim.indices,
|
||||
.type = prim_type,
|
||||
});
|
||||
}
|
||||
mMorphTable[entity].permutations.emplace_back(std::move(result));
|
||||
}
|
||||
|
||||
// Creates a VertexBuffer for a given permutation of "primary indices" (i.e. set of 4 targets). In
|
||||
// some ways this mimics AssetLoader::createPrimitive() but it is simpler and less efficient because
|
||||
// it immediately clones and uploads buffer data.
|
||||
//
|
||||
// TODO: This strategy is inefficient because it results in duplication of data. For example, an
|
||||
// unmorphed UV0 attribute will have duplicated uploads and duplicated GPU data. We will soon be
|
||||
// adding a new API to Filament to fix this.
|
||||
VertexBuffer* MorphHelper::createVertexBuffer(const cgltf_primitive& prim, const UvMap& uvmap,
|
||||
ubyte4 primaryIndices) {
|
||||
|
||||
// Determine the number of vertices by looking at the first usable attribute.
|
||||
const uint32_t vertexCount = [&prim]() {
|
||||
uint32_t count = 0;
|
||||
for (cgltf_size aindex = 0; aindex < prim.attributes_count; aindex++) {
|
||||
const cgltf_attribute& attribute = prim.attributes[aindex];
|
||||
const cgltf_accessor* accessor = attribute.data;
|
||||
if (accessor) {
|
||||
count = accessor->count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}();
|
||||
|
||||
// This creates a copy because we don't know when the user will free the cgltf source data.
|
||||
// For non-morphed vertex buffers, we use a sharing mechanism to prevent copies, but here
|
||||
// we just want to keep it as simple as possible.
|
||||
auto createBufferDescriptor = [](const cgltf_accessor* accessor) {
|
||||
auto bufferData = (const uint8_t*) accessor->buffer_view->buffer->data;
|
||||
const uint8_t* data = computeBindingOffset(accessor) + bufferData;
|
||||
const uint32_t size = computeBindingSize(accessor);
|
||||
uint8_t* clone = (uint8_t*) malloc(size);
|
||||
memcpy(clone, data, size);
|
||||
return VertexBuffer::BufferDescriptor(clone, size, [](void* mem, size_t, void*) {
|
||||
free(mem);
|
||||
});
|
||||
};
|
||||
|
||||
// This is used to populate unused attributes (e.g. UV1 or COLOR) in ubershader mode.
|
||||
// It mimics the dummy buffer in AssetLoader created for non-morphing renderables.
|
||||
auto createDummyBuffer = [vertexCount]() {
|
||||
uint32_t size = sizeof(ubyte4) * vertexCount;
|
||||
uint32_t* dummyData = (uint32_t*) malloc(size);
|
||||
memset(dummyData, 0xff, size);
|
||||
return VertexBuffer::BufferDescriptor(dummyData, size, [](void* mem, size_t, void*) {
|
||||
free(mem);
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: The tangents computation should be invoked by the job system, similar to what we
|
||||
// already do in the non-morphing codepath.
|
||||
auto createTangentsBuffer = [&prim](int morphTarget) {
|
||||
TangentsJob::Params params = {{ &prim, morphTarget }};
|
||||
TangentsJob::run(¶ms);
|
||||
uint32_t size = sizeof(short4) * params.out.vertexCount;
|
||||
short4* quats = params.out.results;
|
||||
return VertexBuffer::BufferDescriptor(quats, size, [](void* mem, size_t, void*) {
|
||||
free(mem);
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: this magic number should be exposed by a static VertexBuffer method or constant.
|
||||
static constexpr int kMaxBufferCount = 16;
|
||||
VertexBuffer::BufferDescriptor buffers[kMaxBufferCount] = {};
|
||||
|
||||
VertexBuffer::Builder vbb;
|
||||
vbb.vertexCount(vertexCount);
|
||||
|
||||
bool hasUv0 = false, hasUv1 = false, hasVertexColor = false, hasNormals = false;
|
||||
int slot = 0;
|
||||
for (cgltf_size aindex = 0; aindex < prim.attributes_count; aindex++) {
|
||||
const cgltf_attribute& attribute = prim.attributes[aindex];
|
||||
const int index = attribute.index;
|
||||
const cgltf_attribute_type atype = attribute.type;
|
||||
const cgltf_accessor* accessor = attribute.data;
|
||||
if (atype == cgltf_attribute_type_tangent) {
|
||||
continue;
|
||||
}
|
||||
if (atype == cgltf_attribute_type_normal) {
|
||||
vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4);
|
||||
vbb.normalized(VertexAttribute::TANGENTS);
|
||||
buffers[slot++] = createTangentsBuffer(TangentsJob::kMorphTargetUnused);
|
||||
hasNormals = true;
|
||||
continue;
|
||||
}
|
||||
if (atype == cgltf_attribute_type_color) {
|
||||
hasVertexColor = true;
|
||||
}
|
||||
VertexAttribute semantic;
|
||||
getVertexAttrType(atype, &semantic);
|
||||
if (atype == cgltf_attribute_type_texcoord) {
|
||||
if (index >= UvMapSize) {
|
||||
continue;
|
||||
}
|
||||
UvSet uvset = uvmap[index];
|
||||
switch (uvset) {
|
||||
case UV0:
|
||||
semantic = VertexAttribute::UV0;
|
||||
hasUv0 = true;
|
||||
break;
|
||||
case UV1:
|
||||
semantic = VertexAttribute::UV1;
|
||||
hasUv1 = true;
|
||||
break;
|
||||
case UNUSED:
|
||||
if (!hasUv0 && getNumUvSets(uvmap) == 0) {
|
||||
semantic = VertexAttribute::UV0;
|
||||
hasUv0 = true;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
VertexBuffer::AttributeType fatype;
|
||||
getElementType(accessor->type, accessor->component_type, &fatype);
|
||||
vbb.attribute(semantic, slot, fatype, 0, accessor->stride);
|
||||
vbb.normalized(semantic, accessor->normalized);
|
||||
buffers[slot++] = createBufferDescriptor(accessor);
|
||||
}
|
||||
|
||||
// If the model is lit but does not have normals, we'll need to generate flat normals.
|
||||
if (prim.material && !prim.material->unlit && !hasNormals) {
|
||||
vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4);
|
||||
vbb.normalized(VertexAttribute::TANGENTS);
|
||||
buffers[slot++] = createTangentsBuffer(TangentsJob::kMorphTargetUnused);
|
||||
cgltf_attribute_type atype = cgltf_attribute_type_normal;
|
||||
}
|
||||
|
||||
constexpr int baseTangentsAttr = (int) VertexAttribute::MORPH_TANGENTS_0;
|
||||
constexpr int basePositionAttr = (int) VertexAttribute::MORPH_POSITION_0;
|
||||
|
||||
for (int targetCount = 0; targetCount < 4; ++targetCount) {
|
||||
cgltf_size targetIndex = primaryIndices[targetCount];
|
||||
if (targetIndex == kUnused) {
|
||||
break;
|
||||
}
|
||||
const cgltf_accessor* previous = nullptr;
|
||||
for (int targetIndex = 0; targetIndex < prim.targets_count; targetIndex++) {
|
||||
const cgltf_morph_target& morphTarget = prim.targets[targetIndex];
|
||||
for (cgltf_size aindex = 0; aindex < morphTarget.attributes_count; aindex++) {
|
||||
const cgltf_attribute& attribute = morphTarget.attributes[aindex];
|
||||
@@ -321,58 +158,78 @@ VertexBuffer* MorphHelper::createVertexBuffer(const cgltf_primitive& prim, const
|
||||
continue;
|
||||
}
|
||||
if (atype == cgltf_attribute_type_normal) {
|
||||
VertexAttribute attr = (VertexAttribute) (baseTangentsAttr + targetCount);
|
||||
vbb.attribute(attr, slot, VertexBuffer::AttributeType::SHORT4);
|
||||
vbb.normalized(attr);
|
||||
buffers[slot++] = createTangentsBuffer(targetIndex);
|
||||
|
||||
// TODO: use JobSystem for this, like what we do for non-morph tangents.
|
||||
TangentsJob job;
|
||||
TangentsJob::Params params = { .in = { &prim, targetIndex } };
|
||||
TangentsJob::run(¶ms);
|
||||
|
||||
if (params.out.results) {
|
||||
const size_t size = params.out.vertexCount * sizeof(short4);
|
||||
BufferObject* bufferObject = BufferObject::Builder().size(size).build(engine);
|
||||
VertexBuffer::BufferDescriptor bd(params.out.results, size, FREE_CALLBACK);
|
||||
bufferObject->setBuffer(engine, std::move(bd));
|
||||
params.out.results = nullptr;
|
||||
targets.push_back({bufferObject, targetIndex, atype});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
VertexBuffer::AttributeType fatype;
|
||||
getElementType(accessor->type, accessor->component_type, &fatype);
|
||||
VertexAttribute attr = (VertexAttribute) (basePositionAttr + targetCount);
|
||||
vbb.attribute(attr, slot, fatype, 0, accessor->stride);
|
||||
vbb.normalized(attr, accessor->normalized);
|
||||
buffers[slot++] = createBufferDescriptor(accessor);
|
||||
if (atype == cgltf_attribute_type_position) {
|
||||
// All position attributes must have the same data type.
|
||||
assert(!previous || previous->component_type == accessor->component_type);
|
||||
assert(!previous || previous->type == accessor->type);
|
||||
previous = accessor;
|
||||
|
||||
// This should always be non-null, but don't crash if the glTF is malformed.
|
||||
if (accessor->buffer_view) {
|
||||
auto bufferData = (const uint8_t*) accessor->buffer_view->buffer->data;
|
||||
assert(bufferData);
|
||||
const uint8_t* data = computeBindingOffset(accessor) + bufferData;
|
||||
const uint32_t size = computeBindingSize(accessor);
|
||||
|
||||
// This creates a copy because we don't know when the user will free the cgltf
|
||||
// source data. For non-morphed vertex buffers, we use a sharing mechanism to
|
||||
// prevent copies, but here we just want to keep it as simple as possible.
|
||||
uint8_t* clone = (uint8_t*) malloc(size);
|
||||
memcpy(clone, data, size);
|
||||
|
||||
BufferObject* bufferObject = BufferObject::Builder().size(size).build(engine);
|
||||
VertexBuffer::BufferDescriptor bd(clone, size, FREE_CALLBACK);
|
||||
bufferObject->setBuffer(engine, std::move(bd));
|
||||
targets.push_back({bufferObject, targetIndex, atype});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next determine if we need to create a dummy buffer for tex coords or vertex color. If so,
|
||||
// create a single dummy buffer and share it. This is inefficient because it assumes the worst
|
||||
// case (i.e. ubershader mode) and assigns the dummy buffer to every unused attribute. We
|
||||
// should instead remember which attributes are actually required by MaterialProvider.
|
||||
|
||||
bool needsDummyData = false;
|
||||
if (!hasUv0) {
|
||||
needsDummyData = true;
|
||||
vbb.attribute(VertexAttribute::UV0, slot, VertexBuffer::AttributeType::USHORT2);
|
||||
vbb.normalized(VertexAttribute::UV0);
|
||||
}
|
||||
if (!hasUv1) {
|
||||
needsDummyData = true;
|
||||
vbb.attribute(VertexAttribute::UV1, slot, VertexBuffer::AttributeType::USHORT2);
|
||||
vbb.normalized(VertexAttribute::UV1);
|
||||
}
|
||||
if (!hasVertexColor) {
|
||||
needsDummyData = true;
|
||||
vbb.attribute(VertexAttribute::COLOR, slot, VertexBuffer::AttributeType::UBYTE4);
|
||||
vbb.normalized(VertexAttribute::COLOR);
|
||||
}
|
||||
if (needsDummyData) {
|
||||
buffers[slot++] = createDummyBuffer();
|
||||
}
|
||||
|
||||
const int bufferCount = slot;
|
||||
|
||||
vbb.bufferCount(bufferCount);
|
||||
VertexBuffer* vertices = vbb.build(*mAsset->mEngine);
|
||||
|
||||
for (int bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex) {
|
||||
if (buffers[bufferIndex].buffer != nullptr) {
|
||||
vertices->setBufferAt(*mAsset->mEngine, bufferIndex, std::move(buffers[bufferIndex]));
|
||||
}
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
// This mimics some of the logic in AssetLoader in order to determine which VertexBuffer slots
|
||||
// correspond to the morph targets.
|
||||
int MorphHelper::determineBaseSlot(const cgltf_primitive& prim) const {
|
||||
int slot = 0;
|
||||
bool hasNormals = false;
|
||||
for (cgltf_size aindex = 0; aindex < prim.attributes_count; aindex++) {
|
||||
const cgltf_attribute& attribute = prim.attributes[aindex];
|
||||
const int index = attribute.index;
|
||||
const cgltf_attribute_type atype = attribute.type;
|
||||
const cgltf_accessor* accessor = attribute.data;
|
||||
if (atype == cgltf_attribute_type_tangent) {
|
||||
continue;
|
||||
}
|
||||
if (atype == cgltf_attribute_type_normal) {
|
||||
slot++;
|
||||
hasNormals = true;
|
||||
continue;
|
||||
}
|
||||
slot++;
|
||||
}
|
||||
|
||||
// If the model has lighting but not normals, then a slot is used for generated flat normals.
|
||||
if (prim.material && !prim.material->unlit && !hasNormals) {
|
||||
slot++;
|
||||
}
|
||||
|
||||
return slot;
|
||||
};
|
||||
|
||||
} // namespace gltfio
|
||||
|
||||
@@ -24,13 +24,14 @@
|
||||
#include <vector>
|
||||
|
||||
struct cgltf_node;
|
||||
struct cgltf_mesh;
|
||||
struct cgltf_primitive;
|
||||
|
||||
namespace gltfio {
|
||||
|
||||
/**
|
||||
* Internal class that partitions lists of morph weights and maintains a cache of VertexBuffer
|
||||
* objects for each partition. This allows gltfio to support up to 255 morph targets.
|
||||
* Internal class that partitions lists of morph weights and maintains a cache of BufferObject
|
||||
* instances. This allows gltfio to support up to 255 morph targets.
|
||||
*
|
||||
* Each partition is associated with an unordered set of 4 (or fewer) morph target indices, which
|
||||
* we call the "primary indices" for that time slice.
|
||||
@@ -48,41 +49,30 @@ public:
|
||||
*/
|
||||
void applyWeights(Entity targetEntity, float const* weights, size_t count) noexcept;
|
||||
|
||||
/**
|
||||
* Disables or enables writes to renderables.
|
||||
*
|
||||
* This allows the MorphHelper cache to be primed without actually affecting any renderables.
|
||||
*/
|
||||
void disableWrites(bool disabled) noexcept { mWritesDisabled = disabled; }
|
||||
|
||||
private:
|
||||
struct GltfPrimitive {
|
||||
filament::VertexBuffer* vertices;
|
||||
filament::IndexBuffer* indices;
|
||||
filament::RenderableManager::PrimitiveType type;
|
||||
struct GltfTarget {
|
||||
filament::BufferObject* bufferObject;
|
||||
int morphTargetIndex;
|
||||
cgltf_attribute_type type;
|
||||
};
|
||||
|
||||
struct Permutation {
|
||||
filament::math::ubyte4 primaryIndices;
|
||||
std::vector<GltfPrimitive> primitives;
|
||||
bool owner;
|
||||
struct GltfPrimitive {
|
||||
filament::VertexBuffer* vertexBuffer;
|
||||
int baseSlot;
|
||||
std::vector<GltfTarget> targets; // TODO: flatten this?
|
||||
};
|
||||
|
||||
struct TableEntry {
|
||||
std::vector<Permutation> permutations;
|
||||
const cgltf_node* sourceNode;
|
||||
std::vector<GltfPrimitive> primitives; // TODO: flatten this?
|
||||
};
|
||||
|
||||
void addPermutation(Entity targetEntity, filament::math::ubyte4 primaryIndices);
|
||||
|
||||
filament::VertexBuffer* createVertexBuffer(const cgltf_primitive& prim, const UvMap& uvmap,
|
||||
filament::math::ubyte4 primaryIndices);
|
||||
void addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, TableEntry* entry);
|
||||
int determineBaseSlot(const cgltf_primitive& prim) const;
|
||||
|
||||
std::vector<float> mPartiallySortedWeights;
|
||||
tsl::robin_map<Entity, TableEntry> mMorphTable;
|
||||
const FFilamentAsset* mAsset;
|
||||
const FFilamentInstance* mInstance;
|
||||
bool mWritesDisabled = false;
|
||||
};
|
||||
|
||||
} // namespace gltfio
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "TangentsJob.h"
|
||||
#include "upcast.h"
|
||||
|
||||
#include <filament/BufferObject.h>
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/IndexBuffer.h>
|
||||
#include <filament/MaterialInstance.h>
|
||||
@@ -431,8 +432,11 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
|
||||
const uint8_t* data = computeBindingOffset(accessor) + bufferData;
|
||||
const uint32_t size = computeBindingSize(accessor);
|
||||
if (slot.vertexBuffer) {
|
||||
VertexBuffer::BufferDescriptor bd(data, size, uploadCallback, uploadUserdata(asset));
|
||||
slot.vertexBuffer->setBufferAt(engine, slot.bufferIndex, std::move(bd));
|
||||
BufferObject* bo = BufferObject::Builder().size(size).build(engine);
|
||||
asset->mBufferObjects.push_back(bo);
|
||||
bo->setBuffer(engine, BufferDescriptor(data, size,
|
||||
uploadCallback, uploadUserdata(asset)));
|
||||
slot.vertexBuffer->setBufferObjectAt(engine, slot.bufferIndex, bo);
|
||||
continue;
|
||||
}
|
||||
assert(slot.indexBuffer);
|
||||
@@ -846,9 +850,12 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) {
|
||||
|
||||
// Finally, upload quaternions to the GPU from the main thread.
|
||||
for (Params& params : jobParams) {
|
||||
VertexBuffer::BufferDescriptor bd(params.out.results,
|
||||
params.out.vertexCount * sizeof(short4), FREE_CALLBACK);
|
||||
params.context.vb->setBufferAt(*mEngine, params.context.slot, std::move(bd));
|
||||
BufferObject* bo = BufferObject::Builder()
|
||||
.size(params.out.vertexCount * sizeof(short4)).build(*mEngine);
|
||||
asset->mBufferObjects.push_back(bo);
|
||||
bo->setBuffer(*mEngine, BufferDescriptor(
|
||||
params.out.results, bo->getByteCount(), FREE_CALLBACK));
|
||||
params.context.vb->setBufferObjectAt(*mEngine, params.context.slot, bo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,8 +875,10 @@ void ResourceLoader::applySparseData(FFilamentAsset* asset) const {
|
||||
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));
|
||||
BufferObject* bo = BufferObject::Builder().size(numBytes).build(*asset->mEngine);
|
||||
asset->mBufferObjects.push_back(bo);
|
||||
bo->setBuffer(*pImpl->mEngine, BufferDescriptor(generated, numBytes, FREE_CALLBACK));
|
||||
slot.vertexBuffer->setBufferObjectAt(*pImpl->mEngine, slot.bufferIndex, bo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user