gltfio: Use BufferObject API, simplify MorphHelper. (#3737)

* gltfio: Use BufferObject API, simplify MorphHelper.

* Repair Android build.
This commit is contained in:
Philip Rideout
2021-04-05 13:00:39 -07:00
committed by GitHub
parent a139d7fc01
commit 82fcb656d9
8 changed files with 157 additions and 312 deletions

View File

@@ -2,6 +2,7 @@ LIBFILAMENT {
global:
Java_com_google_android_filament_*;
JNI*;
*filament*BufferObject*;
*filament*Camera*;
*filament*Color*;
*filament*Exposure*;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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(&params);
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(&params);
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

View File

@@ -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

View File

@@ -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);
}
}